diff --git a/README.md b/README.md
index c8008d8e..2e3dacd9 100644
--- a/README.md
+++ b/README.md
@@ -66,7 +66,7 @@ Now it also try to support p2p connect.
 
 frp is under development and you can try it with latest release version. Master branch for releasing stable version when dev branch for developing.
 
-**We may change any protocol and can't promise backward compatible. Please check the release log when upgrading.**
+**We may change any protocol and can't promise backward compatibility. Please check the release log when upgrading.**
 
 ## Architecture
 
@@ -265,6 +265,7 @@ Configure frps same as above.
   plugin_crt_path = ./server.crt
   plugin_key_path = ./server.key
   plugin_host_header_rewrite = 127.0.0.1
+  plugin_header_X-From-Where = frp
   ```
 
 2. Visit `https://test.yourdomain.com`.
diff --git a/README_zh.md b/README_zh.md
index a918930d..a9e03cb2 100644
--- a/README_zh.md
+++ b/README_zh.md
@@ -129,7 +129,7 @@ master 分支用于发布稳定版本,dev 分支用于开发,您可以尝试
   vhost_http_port = 8080
   ```
 
-2. 启动 frps;
+2. 启动 frps:
 
   `./frps -c ./frps.ini`
 
@@ -270,6 +270,7 @@ frps 的部署步骤同上。
   plugin_crt_path = ./server.crt
   plugin_key_path = ./server.key
   plugin_host_header_rewrite = 127.0.0.1
+  plugin_header_X-From-Where = frp
   ```
 
 2. 通过浏览器访问 `https://test.yourdomain.com` 即可。
diff --git a/client/admin.go b/client/admin.go
index 90456857..b3e3f81a 100644
--- a/client/admin.go
+++ b/client/admin.go
@@ -21,7 +21,6 @@ import (
 	"time"
 
 	"github.com/fatedier/frp/assets"
-	"github.com/fatedier/frp/g"
 	frpNet "github.com/fatedier/frp/utils/net"
 
 	"github.com/gorilla/mux"
@@ -36,7 +35,7 @@ func (svr *Service) RunAdminServer(addr string, port int) (err error) {
 	// url router
 	router := mux.NewRouter()
 
-	user, passwd := g.GlbClientCfg.AdminUser, g.GlbClientCfg.AdminPwd
+	user, passwd := svr.cfg.AdminUser, svr.cfg.AdminPwd
 	router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware)
 
 	// api, see dashboard_api.go
diff --git a/client/admin_api.go b/client/admin_api.go
index 4d7205bb..af61b395 100644
--- a/client/admin_api.go
+++ b/client/admin_api.go
@@ -23,7 +23,6 @@ import (
 	"strings"
 
 	"github.com/fatedier/frp/client/proxy"
-	"github.com/fatedier/frp/g"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/utils/log"
 )
@@ -47,7 +46,7 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
 		}
 	}()
 
-	content, err := config.GetRenderedConfFromFile(g.GlbClientCfg.CfgFile)
+	content, err := config.GetRenderedConfFromFile(svr.cfgFile)
 	if err != nil {
 		res.Code = 400
 		res.Msg = err.Error()
@@ -55,7 +54,7 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	newCommonCfg, err := config.UnmarshalClientConfFromIni(nil, content)
+	newCommonCfg, err := config.UnmarshalClientConfFromIni(content)
 	if err != nil {
 		res.Code = 400
 		res.Msg = err.Error()
@@ -63,7 +62,7 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(g.GlbClientCfg.User, content, newCommonCfg.Start)
+	pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(svr.cfg.User, content, newCommonCfg.Start)
 	if err != nil {
 		res.Code = 400
 		res.Msg = err.Error()
@@ -107,7 +106,7 @@ func (a ByProxyStatusResp) Len() int           { return len(a) }
 func (a ByProxyStatusResp) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
 func (a ByProxyStatusResp) Less(i, j int) bool { return strings.Compare(a[i].Name, a[j].Name) < 0 }
 
-func NewProxyStatusResp(status *proxy.ProxyStatus) ProxyStatusResp {
+func NewProxyStatusResp(status *proxy.ProxyStatus, serverAddr string) ProxyStatusResp {
 	psr := ProxyStatusResp{
 		Name:   status.Name,
 		Type:   status.Type,
@@ -121,18 +120,18 @@ func NewProxyStatusResp(status *proxy.ProxyStatus) ProxyStatusResp {
 		}
 		psr.Plugin = cfg.Plugin
 		if status.Err != "" {
-			psr.RemoteAddr = fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, cfg.RemotePort)
+			psr.RemoteAddr = fmt.Sprintf("%s:%d", serverAddr, cfg.RemotePort)
 		} else {
-			psr.RemoteAddr = g.GlbClientCfg.ServerAddr + status.RemoteAddr
+			psr.RemoteAddr = serverAddr + status.RemoteAddr
 		}
 	case *config.UdpProxyConf:
 		if cfg.LocalPort != 0 {
 			psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort)
 		}
 		if status.Err != "" {
-			psr.RemoteAddr = fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, cfg.RemotePort)
+			psr.RemoteAddr = fmt.Sprintf("%s:%d", serverAddr, cfg.RemotePort)
 		} else {
-			psr.RemoteAddr = g.GlbClientCfg.ServerAddr + status.RemoteAddr
+			psr.RemoteAddr = serverAddr + status.RemoteAddr
 		}
 	case *config.HttpProxyConf:
 		if cfg.LocalPort != 0 {
@@ -184,17 +183,17 @@ func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
 	for _, status := range ps {
 		switch status.Type {
 		case "tcp":
-			res.Tcp = append(res.Tcp, NewProxyStatusResp(status))
+			res.Tcp = append(res.Tcp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
 		case "udp":
-			res.Udp = append(res.Udp, NewProxyStatusResp(status))
+			res.Udp = append(res.Udp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
 		case "http":
-			res.Http = append(res.Http, NewProxyStatusResp(status))
+			res.Http = append(res.Http, NewProxyStatusResp(status, svr.cfg.ServerAddr))
 		case "https":
-			res.Https = append(res.Https, NewProxyStatusResp(status))
+			res.Https = append(res.Https, NewProxyStatusResp(status, svr.cfg.ServerAddr))
 		case "stcp":
-			res.Stcp = append(res.Stcp, NewProxyStatusResp(status))
+			res.Stcp = append(res.Stcp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
 		case "xtcp":
-			res.Xtcp = append(res.Xtcp, NewProxyStatusResp(status))
+			res.Xtcp = append(res.Xtcp, NewProxyStatusResp(status, svr.cfg.ServerAddr))
 		}
 	}
 	sort.Sort(ByProxyStatusResp(res.Tcp))
@@ -219,14 +218,14 @@ func (svr *Service) apiGetConfig(w http.ResponseWriter, r *http.Request) {
 		}
 	}()
 
-	if g.GlbClientCfg.CfgFile == "" {
+	if svr.cfgFile == "" {
 		res.Code = 400
 		res.Msg = "frpc has no config file path"
 		log.Warn("%s", res.Msg)
 		return
 	}
 
-	content, err := config.GetRenderedConfFromFile(g.GlbClientCfg.CfgFile)
+	content, err := config.GetRenderedConfFromFile(svr.cfgFile)
 	if err != nil {
 		res.Code = 400
 		res.Msg = err.Error()
@@ -277,7 +276,7 @@ func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
 
 	// get token from origin content
 	token := ""
-	b, err := ioutil.ReadFile(g.GlbClientCfg.CfgFile)
+	b, err := ioutil.ReadFile(svr.cfgFile)
 	if err != nil {
 		res.Code = 400
 		res.Msg = err.Error()
@@ -316,7 +315,7 @@ func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
 	}
 	content = strings.Join(newRows, "\n")
 
-	err = ioutil.WriteFile(g.GlbClientCfg.CfgFile, []byte(content), 0644)
+	err = ioutil.WriteFile(svr.cfgFile, []byte(content), 0644)
 	if err != nil {
 		res.Code = 500
 		res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err)
diff --git a/client/control.go b/client/control.go
index 6a12b8c1..30992f9a 100644
--- a/client/control.go
+++ b/client/control.go
@@ -23,7 +23,6 @@ import (
 	"time"
 
 	"github.com/fatedier/frp/client/proxy"
-	"github.com/fatedier/frp/g"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/msg"
 	"github.com/fatedier/frp/utils/log"
@@ -65,16 +64,24 @@ type Control struct {
 	// last time got the Pong message
 	lastPong time.Time
 
+	// The client configuration
+	clientCfg config.ClientCommonConf
+
 	readerShutdown     *shutdown.Shutdown
 	writerShutdown     *shutdown.Shutdown
 	msgHandlerShutdown *shutdown.Shutdown
 
+	// The UDP port that the server is listening on
+	serverUDPPort int
+
 	mu sync.RWMutex
 
 	log.Logger
 }
 
-func NewControl(runId string, conn frpNet.Conn, session *fmux.Session, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) *Control {
+func NewControl(runId string, conn frpNet.Conn, session *fmux.Session, clientCfg config.ClientCommonConf,
+	pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf, serverUDPPort int) *Control {
+
 	ctl := &Control{
 		runId:              runId,
 		conn:               conn,
@@ -84,12 +91,14 @@ func NewControl(runId string, conn frpNet.Conn, session *fmux.Session, pxyCfgs m
 		readCh:             make(chan msg.Message, 100),
 		closedCh:           make(chan struct{}),
 		closedDoneCh:       make(chan struct{}),
+		clientCfg:          clientCfg,
 		readerShutdown:     shutdown.New(),
 		writerShutdown:     shutdown.New(),
 		msgHandlerShutdown: shutdown.New(),
+		serverUDPPort:      serverUDPPort,
 		Logger:             log.NewPrefixLogger(""),
 	}
-	ctl.pm = proxy.NewProxyManager(ctl.sendCh, runId)
+	ctl.pm = proxy.NewProxyManager(ctl.sendCh, runId, clientCfg, serverUDPPort)
 
 	ctl.vm = NewVisitorManager(ctl)
 	ctl.vm.Reload(visitorCfgs)
@@ -161,7 +170,7 @@ func (ctl *Control) ClosedDoneCh() <-chan struct{} {
 
 // connectServer return a new connection to frps
 func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
-	if g.GlbClientCfg.TcpMux {
+	if ctl.clientCfg.TcpMux {
 		stream, errRet := ctl.session.OpenStream()
 		if errRet != nil {
 			err = errRet
@@ -171,13 +180,13 @@ func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
 		conn = frpNet.WrapConn(stream)
 	} else {
 		var tlsConfig *tls.Config
-		if g.GlbClientCfg.TLSEnable {
+		if ctl.clientCfg.TLSEnable {
 			tlsConfig = &tls.Config{
 				InsecureSkipVerify: true,
 			}
 		}
-		conn, err = frpNet.ConnectServerByProxyWithTLS(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
-			fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort), tlsConfig)
+		conn, err = frpNet.ConnectServerByProxyWithTLS(ctl.clientCfg.HttpProxy, ctl.clientCfg.Protocol,
+			fmt.Sprintf("%s:%d", ctl.clientCfg.ServerAddr, ctl.clientCfg.ServerPort), tlsConfig)
 		if err != nil {
 			ctl.Warn("start new connection to server error: %v", err)
 			return
@@ -197,7 +206,7 @@ func (ctl *Control) reader() {
 	defer ctl.readerShutdown.Done()
 	defer close(ctl.closedCh)
 
-	encReader := crypto.NewReader(ctl.conn, []byte(g.GlbClientCfg.Token))
+	encReader := crypto.NewReader(ctl.conn, []byte(ctl.clientCfg.Token))
 	for {
 		if m, err := msg.ReadMsg(encReader); err != nil {
 			if err == io.EOF {
@@ -217,7 +226,7 @@ func (ctl *Control) reader() {
 // writer writes messages got from sendCh to frps
 func (ctl *Control) writer() {
 	defer ctl.writerShutdown.Done()
-	encWriter, err := crypto.NewWriter(ctl.conn, []byte(g.GlbClientCfg.Token))
+	encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.clientCfg.Token))
 	if err != nil {
 		ctl.conn.Error("crypto new writer error: %v", err)
 		ctl.conn.Close()
@@ -246,7 +255,7 @@ func (ctl *Control) msgHandler() {
 	}()
 	defer ctl.msgHandlerShutdown.Done()
 
-	hbSend := time.NewTicker(time.Duration(g.GlbClientCfg.HeartBeatInterval) * time.Second)
+	hbSend := time.NewTicker(time.Duration(ctl.clientCfg.HeartBeatInterval) * time.Second)
 	defer hbSend.Stop()
 	hbCheck := time.NewTicker(time.Second)
 	defer hbCheck.Stop()
@@ -260,7 +269,7 @@ func (ctl *Control) msgHandler() {
 			ctl.Debug("send heartbeat to server")
 			ctl.sendCh <- &msg.Ping{}
 		case <-hbCheck.C:
-			if time.Since(ctl.lastPong) > time.Duration(g.GlbClientCfg.HeartBeatTimeout)*time.Second {
+			if time.Since(ctl.lastPong) > time.Duration(ctl.clientCfg.HeartBeatTimeout)*time.Second {
 				ctl.Warn("heartbeat timeout")
 				// let reader() stop
 				ctl.conn.Close()
diff --git a/client/proxy/proxy.go b/client/proxy/proxy.go
index bc230f40..f5e1b603 100644
--- a/client/proxy/proxy.go
+++ b/client/proxy/proxy.go
@@ -25,7 +25,6 @@ import (
 	"sync"
 	"time"
 
-	"github.com/fatedier/frp/g"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/msg"
 	"github.com/fatedier/frp/models/plugin"
@@ -51,9 +50,11 @@ type Proxy interface {
 	log.Logger
 }
 
-func NewProxy(pxyConf config.ProxyConf) (pxy Proxy) {
+func NewProxy(pxyConf config.ProxyConf, clientCfg config.ClientCommonConf, serverUDPPort int) (pxy Proxy) {
 	baseProxy := BaseProxy{
-		Logger: log.NewPrefixLogger(pxyConf.GetBaseInfo().ProxyName),
+		Logger:        log.NewPrefixLogger(pxyConf.GetBaseInfo().ProxyName),
+		clientCfg:     clientCfg,
+		serverUDPPort: serverUDPPort,
 	}
 	switch cfg := pxyConf.(type) {
 	case *config.TcpProxyConf:
@@ -91,8 +92,10 @@ func NewProxy(pxyConf config.ProxyConf) (pxy Proxy) {
 }
 
 type BaseProxy struct {
-	closed bool
-	mu     sync.RWMutex
+	closed        bool
+	mu            sync.RWMutex
+	clientCfg     config.ClientCommonConf
+	serverUDPPort int
 	log.Logger
 }
 
@@ -122,7 +125,7 @@ func (pxy *TcpProxy) Close() {
 
 func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
 	HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
-		[]byte(g.GlbClientCfg.Token), m)
+		[]byte(pxy.clientCfg.Token), m)
 }
 
 // HTTP
@@ -151,7 +154,7 @@ func (pxy *HttpProxy) Close() {
 
 func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
 	HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
-		[]byte(g.GlbClientCfg.Token), m)
+		[]byte(pxy.clientCfg.Token), m)
 }
 
 // HTTPS
@@ -180,7 +183,7 @@ func (pxy *HttpsProxy) Close() {
 
 func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
 	HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
-		[]byte(g.GlbClientCfg.Token), m)
+		[]byte(pxy.clientCfg.Token), m)
 }
 
 // STCP
@@ -209,7 +212,7 @@ func (pxy *StcpProxy) Close() {
 
 func (pxy *StcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
 	HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
-		[]byte(g.GlbClientCfg.Token), m)
+		[]byte(pxy.clientCfg.Token), m)
 }
 
 // XTCP
@@ -250,7 +253,7 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
 		Sid:       natHoleSidMsg.Sid,
 	}
 	raddr, _ := net.ResolveUDPAddr("udp",
-		fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerUdpPort))
+		fmt.Sprintf("%s:%d", pxy.clientCfg.ServerAddr, pxy.serverUDPPort))
 	clientConn, err := net.DialUDP("udp", nil, raddr)
 	defer clientConn.Close()
 
@@ -518,7 +521,7 @@ func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.
 				DestinationPort:    m.DstPort,
 			}
 
-			if h.SourceAddress.To16() == nil {
+			if strings.Contains(m.SrcAddr, ".") {
 				h.TransportProtocol = pp.TCPv4
 			} else {
 				h.TransportProtocol = pp.TCPv6
diff --git a/client/proxy/proxy_manager.go b/client/proxy/proxy_manager.go
index fb230b3e..1d0d8786 100644
--- a/client/proxy/proxy_manager.go
+++ b/client/proxy/proxy_manager.go
@@ -20,17 +20,24 @@ type ProxyManager struct {
 	closed bool
 	mu     sync.RWMutex
 
+	clientCfg config.ClientCommonConf
+
+	// The UDP port that the server is listening on
+	serverUDPPort int
+
 	logPrefix string
 	log.Logger
 }
 
-func NewProxyManager(msgSendCh chan (msg.Message), logPrefix string) *ProxyManager {
+func NewProxyManager(msgSendCh chan (msg.Message), logPrefix string, clientCfg config.ClientCommonConf, serverUDPPort int) *ProxyManager {
 	return &ProxyManager{
-		proxies:   make(map[string]*ProxyWrapper),
-		sendCh:    msgSendCh,
-		closed:    false,
-		logPrefix: logPrefix,
-		Logger:    log.NewPrefixLogger(logPrefix),
+		proxies:       make(map[string]*ProxyWrapper),
+		sendCh:        msgSendCh,
+		closed:        false,
+		clientCfg:     clientCfg,
+		serverUDPPort: serverUDPPort,
+		logPrefix:     logPrefix,
+		Logger:        log.NewPrefixLogger(logPrefix),
 	}
 }
 
@@ -126,7 +133,7 @@ func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf) {
 	addPxyNames := make([]string, 0)
 	for name, cfg := range pxyCfgs {
 		if _, ok := pm.proxies[name]; !ok {
-			pxy := NewProxyWrapper(cfg, pm.HandleEvent, pm.logPrefix)
+			pxy := NewProxyWrapper(cfg, pm.clientCfg, pm.HandleEvent, pm.logPrefix, pm.serverUDPPort)
 			pm.proxies[name] = pxy
 			addPxyNames = append(addPxyNames, name)
 
diff --git a/client/proxy/proxy_wrapper.go b/client/proxy/proxy_wrapper.go
index 0b29e481..b02e5291 100644
--- a/client/proxy/proxy_wrapper.go
+++ b/client/proxy/proxy_wrapper.go
@@ -65,7 +65,7 @@ type ProxyWrapper struct {
 	log.Logger
 }
 
-func NewProxyWrapper(cfg config.ProxyConf, eventHandler event.EventHandler, logPrefix string) *ProxyWrapper {
+func NewProxyWrapper(cfg config.ProxyConf, clientCfg config.ClientCommonConf, eventHandler event.EventHandler, logPrefix string, serverUDPPort int) *ProxyWrapper {
 	baseInfo := cfg.GetBaseInfo()
 	pw := &ProxyWrapper{
 		ProxyStatus: ProxyStatus{
@@ -90,7 +90,7 @@ func NewProxyWrapper(cfg config.ProxyConf, eventHandler event.EventHandler, logP
 		pw.Trace("enable health check monitor")
 	}
 
-	pw.pxy = NewProxy(pw.Cfg)
+	pw.pxy = NewProxy(pw.Cfg, clientCfg, serverUDPPort)
 	return pw
 }
 
diff --git a/client/service.go b/client/service.go
index f21be4fe..9f86ec7a 100644
--- a/client/service.go
+++ b/client/service.go
@@ -24,7 +24,6 @@ import (
 	"time"
 
 	"github.com/fatedier/frp/assets"
-	"github.com/fatedier/frp/g"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/msg"
 	"github.com/fatedier/frp/utils/log"
@@ -35,6 +34,7 @@ import (
 	fmux "github.com/hashicorp/yamux"
 )
 
+// Service is a client service.
 type Service struct {
 	// uniq id got from frps, attach it in loginMsg
 	runId string
@@ -43,23 +43,27 @@ type Service struct {
 	ctl   *Control
 	ctlMu sync.RWMutex
 
+	cfg         config.ClientCommonConf
 	pxyCfgs     map[string]config.ProxyConf
 	visitorCfgs map[string]config.VisitorConf
 	cfgMu       sync.RWMutex
 
+	// The configuration file used to initialize this client, or an empty
+	// string if no configuration file was used.
+	cfgFile string
+
+	// This is configured by the login response from frps
+	serverUDPPort int
+
 	exit     uint32 // 0 means not exit
 	closedCh chan int
 }
 
-func NewService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) (svr *Service, err error) {
-	// Init assets
-	err = assets.Load("")
-	if err != nil {
-		err = fmt.Errorf("Load assets error: %v", err)
-		return
-	}
-
+// NewService creates a new client service with the given configuration.
+func NewService(cfg config.ClientCommonConf, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf, cfgFile string) (svr *Service, err error) {
 	svr = &Service{
+		cfg:         cfg,
+		cfgFile:     cfgFile,
 		pxyCfgs:     pxyCfgs,
 		visitorCfgs: visitorCfgs,
 		exit:        0,
@@ -83,14 +87,14 @@ func (svr *Service) Run() error {
 
 			// if login_fail_exit is true, just exit this program
 			// otherwise sleep a while and try again to connect to server
-			if g.GlbClientCfg.LoginFailExit {
+			if svr.cfg.LoginFailExit {
 				return err
 			} else {
 				time.Sleep(10 * time.Second)
 			}
 		} else {
 			// login success
-			ctl := NewControl(svr.runId, conn, session, svr.pxyCfgs, svr.visitorCfgs)
+			ctl := NewControl(svr.runId, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort)
 			ctl.Run()
 			svr.ctlMu.Lock()
 			svr.ctl = ctl
@@ -101,12 +105,18 @@ func (svr *Service) Run() error {
 
 	go svr.keepControllerWorking()
 
-	if g.GlbClientCfg.AdminPort != 0 {
-		err := svr.RunAdminServer(g.GlbClientCfg.AdminAddr, g.GlbClientCfg.AdminPort)
+	if svr.cfg.AdminPort != 0 {
+		// Init admin server assets
+		err := assets.Load(svr.cfg.AssetsDir)
+		if err != nil {
+			return fmt.Errorf("Load assets error: %v", err)
+		}
+
+		err = svr.RunAdminServer(svr.cfg.AdminAddr, svr.cfg.AdminPort)
 		if err != nil {
 			log.Warn("run admin server error: %v", err)
 		}
-		log.Info("admin server listen on %s:%d", g.GlbClientCfg.AdminAddr, g.GlbClientCfg.AdminPort)
+		log.Info("admin server listen on %s:%d", svr.cfg.AdminAddr, svr.cfg.AdminPort)
 	}
 
 	<-svr.closedCh
@@ -138,7 +148,7 @@ func (svr *Service) keepControllerWorking() {
 			// reconnect success, init delayTime
 			delayTime = time.Second
 
-			ctl := NewControl(svr.runId, conn, session, svr.pxyCfgs, svr.visitorCfgs)
+			ctl := NewControl(svr.runId, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort)
 			ctl.Run()
 			svr.ctlMu.Lock()
 			svr.ctl = ctl
@@ -153,13 +163,13 @@ func (svr *Service) keepControllerWorking() {
 // session: if it's not nil, using tcp mux
 func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error) {
 	var tlsConfig *tls.Config
-	if g.GlbClientCfg.TLSEnable {
+	if svr.cfg.TLSEnable {
 		tlsConfig = &tls.Config{
 			InsecureSkipVerify: true,
 		}
 	}
-	conn, err = frpNet.ConnectServerByProxyWithTLS(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
-		fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort), tlsConfig)
+	conn, err = frpNet.ConnectServerByProxyWithTLS(svr.cfg.HttpProxy, svr.cfg.Protocol,
+		fmt.Sprintf("%s:%d", svr.cfg.ServerAddr, svr.cfg.ServerPort), tlsConfig)
 	if err != nil {
 		return
 	}
@@ -173,7 +183,7 @@ func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error)
 		}
 	}()
 
-	if g.GlbClientCfg.TcpMux {
+	if svr.cfg.TcpMux {
 		fmuxCfg := fmux.DefaultConfig()
 		fmuxCfg.KeepAliveInterval = 20 * time.Second
 		fmuxCfg.LogOutput = ioutil.Discard
@@ -194,10 +204,10 @@ func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error)
 	loginMsg := &msg.Login{
 		Arch:         runtime.GOARCH,
 		Os:           runtime.GOOS,
-		PoolCount:    g.GlbClientCfg.PoolCount,
-		User:         g.GlbClientCfg.User,
+		PoolCount:    svr.cfg.PoolCount,
+		User:         svr.cfg.User,
 		Version:      version.Full(),
-		PrivilegeKey: util.GetAuthKey(g.GlbClientCfg.Token, now),
+		PrivilegeKey: util.GetAuthKey(svr.cfg.Token, now),
 		Timestamp:    now,
 		RunId:        svr.runId,
 	}
@@ -220,7 +230,7 @@ func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error)
 	}
 
 	svr.runId = loginRespMsg.RunId
-	g.GlbClientCfg.ServerUdpPort = loginRespMsg.ServerUdpPort
+	svr.serverUDPPort = loginRespMsg.ServerUdpPort
 	log.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunId, loginRespMsg.ServerUdpPort)
 	return
 }
diff --git a/client/visitor.go b/client/visitor.go
index 6eb3688c..773aa115 100644
--- a/client/visitor.go
+++ b/client/visitor.go
@@ -23,7 +23,6 @@ import (
 	"sync"
 	"time"
 
-	"github.com/fatedier/frp/g"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/msg"
 	"github.com/fatedier/frp/utils/log"
@@ -193,13 +192,13 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
 	defer userConn.Close()
 
 	sv.Debug("get a new xtcp user connection")
-	if g.GlbClientCfg.ServerUdpPort == 0 {
+	if sv.ctl.serverUDPPort == 0 {
 		sv.Error("xtcp is not supported by server")
 		return
 	}
 
 	raddr, err := net.ResolveUDPAddr("udp",
-		fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerUdpPort))
+		fmt.Sprintf("%s:%d", sv.ctl.clientCfg.ServerAddr, sv.ctl.serverUDPPort))
 	if err != nil {
 		sv.Error("resolve server UDP addr error")
 		return
diff --git a/cmd/frpc/main.go b/cmd/frpc/main.go
index ba3d932a..443c110c 100644
--- a/cmd/frpc/main.go
+++ b/cmd/frpc/main.go
@@ -15,6 +15,9 @@
 package main
 
 import (
+	"math/rand"
+	"time"
+
 	_ "github.com/fatedier/frp/assets/frpc/statik"
 	"github.com/fatedier/frp/cmd/frpc/sub"
 
@@ -23,6 +26,7 @@ import (
 
 func main() {
 	crypto.DefaultSalt = "frp"
+	rand.Seed(time.Now().UnixNano())
 
 	sub.Execute()
 }
diff --git a/cmd/frpc/sub/http.go b/cmd/frpc/sub/http.go
index 08e915cd..081102cb 100644
--- a/cmd/frpc/sub/http.go
+++ b/cmd/frpc/sub/http.go
@@ -33,6 +33,7 @@ func init() {
 	httpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
 	httpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
 	httpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
+	httpCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
 
 	httpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
 	httpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
@@ -53,7 +54,7 @@ var httpCmd = &cobra.Command{
 	Use:   "http",
 	Short: "Run frpc with a single http proxy",
 	RunE: func(cmd *cobra.Command, args []string) error {
-		err := parseClientCommonCfg(CfgFileTypeCmd, "")
+		clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
 		if err != nil {
 			fmt.Println(err)
 			os.Exit(1)
@@ -86,7 +87,7 @@ var httpCmd = &cobra.Command{
 		proxyConfs := map[string]config.ProxyConf{
 			cfg.ProxyName: cfg,
 		}
-		err = startService(proxyConfs, nil)
+		err = startService(clientCfg, proxyConfs, nil, "")
 		if err != nil {
 			fmt.Println(err)
 			os.Exit(1)
diff --git a/cmd/frpc/sub/https.go b/cmd/frpc/sub/https.go
index 5eb6a7d0..7d5fbe8b 100644
--- a/cmd/frpc/sub/https.go
+++ b/cmd/frpc/sub/https.go
@@ -33,6 +33,7 @@ func init() {
 	httpsCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
 	httpsCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
 	httpsCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
+	httpsCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
 
 	httpsCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
 	httpsCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
@@ -49,7 +50,7 @@ var httpsCmd = &cobra.Command{
 	Use:   "https",
 	Short: "Run frpc with a single https proxy",
 	RunE: func(cmd *cobra.Command, args []string) error {
-		err := parseClientCommonCfg(CfgFileTypeCmd, "")
+		clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
 		if err != nil {
 			fmt.Println(err)
 			os.Exit(1)
@@ -78,7 +79,7 @@ var httpsCmd = &cobra.Command{
 		proxyConfs := map[string]config.ProxyConf{
 			cfg.ProxyName: cfg,
 		}
-		err = startService(proxyConfs, nil)
+		err = startService(clientCfg, proxyConfs, nil, "")
 		if err != nil {
 			fmt.Println(err)
 			os.Exit(1)
diff --git a/cmd/frpc/sub/reload.go b/cmd/frpc/sub/reload.go
index 46118c3e..f3514754 100644
--- a/cmd/frpc/sub/reload.go
+++ b/cmd/frpc/sub/reload.go
@@ -24,7 +24,6 @@ import (
 
 	"github.com/spf13/cobra"
 
-	"github.com/fatedier/frp/g"
 	"github.com/fatedier/frp/models/config"
 )
 
@@ -42,13 +41,13 @@ var reloadCmd = &cobra.Command{
 			os.Exit(1)
 		}
 
-		err = parseClientCommonCfg(CfgFileTypeIni, iniContent)
+		clientCfg, err := parseClientCommonCfg(CfgFileTypeIni, iniContent)
 		if err != nil {
 			fmt.Println(err)
 			os.Exit(1)
 		}
 
-		err = reload()
+		err = reload(clientCfg)
 		if err != nil {
 			fmt.Printf("frpc reload error: %v\n", err)
 			os.Exit(1)
@@ -58,19 +57,19 @@ var reloadCmd = &cobra.Command{
 	},
 }
 
-func reload() error {
-	if g.GlbClientCfg.AdminPort == 0 {
+func reload(clientCfg config.ClientCommonConf) error {
+	if clientCfg.AdminPort == 0 {
 		return fmt.Errorf("admin_port shoud be set if you want to use reload feature")
 	}
 
 	req, err := http.NewRequest("GET", "http://"+
-		g.GlbClientCfg.AdminAddr+":"+fmt.Sprintf("%d", g.GlbClientCfg.AdminPort)+"/api/reload", nil)
+		clientCfg.AdminAddr+":"+fmt.Sprintf("%d", clientCfg.AdminPort)+"/api/reload", nil)
 	if err != nil {
 		return err
 	}
 
-	authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(g.GlbClientCfg.AdminUser+":"+
-		g.GlbClientCfg.AdminPwd))
+	authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(clientCfg.AdminUser+":"+
+		clientCfg.AdminPwd))
 
 	req.Header.Add("Authorization", authStr)
 	resp, err := http.DefaultClient.Do(req)
diff --git a/cmd/frpc/sub/root.go b/cmd/frpc/sub/root.go
index 61d9b3dd..82a906bd 100644
--- a/cmd/frpc/sub/root.go
+++ b/cmd/frpc/sub/root.go
@@ -28,7 +28,6 @@ import (
 	"github.com/spf13/cobra"
 
 	"github.com/fatedier/frp/client"
-	"github.com/fatedier/frp/g"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/utils/log"
 	"github.com/fatedier/frp/utils/version"
@@ -43,13 +42,14 @@ var (
 	cfgFile     string
 	showVersion bool
 
-	serverAddr string
-	user       string
-	protocol   string
-	token      string
-	logLevel   string
-	logFile    string
-	logMaxDays int
+	serverAddr      string
+	user            string
+	protocol        string
+	token           string
+	logLevel        string
+	logFile         string
+	logMaxDays      int
+	disableLogColor bool
 
 	proxyName         string
 	localIp           string
@@ -113,59 +113,62 @@ func handleSignal(svr *client.Service) {
 	close(kcpDoneCh)
 }
 
-func parseClientCommonCfg(fileType int, content string) (err error) {
+func parseClientCommonCfg(fileType int, content string) (cfg config.ClientCommonConf, err error) {
 	if fileType == CfgFileTypeIni {
-		err = parseClientCommonCfgFromIni(content)
+		cfg, err = parseClientCommonCfgFromIni(content)
 	} else if fileType == CfgFileTypeCmd {
-		err = parseClientCommonCfgFromCmd()
+		cfg, err = parseClientCommonCfgFromCmd()
 	}
 	if err != nil {
 		return
 	}
 
-	err = g.GlbClientCfg.ClientCommonConf.Check()
+	err = cfg.Check()
 	if err != nil {
 		return
 	}
 	return
 }
 
-func parseClientCommonCfgFromIni(content string) (err error) {
-	cfg, err := config.UnmarshalClientConfFromIni(&g.GlbClientCfg.ClientCommonConf, content)
+func parseClientCommonCfgFromIni(content string) (config.ClientCommonConf, error) {
+	cfg, err := config.UnmarshalClientConfFromIni(content)
 	if err != nil {
-		return err
+		return config.ClientCommonConf{}, err
 	}
-	g.GlbClientCfg.ClientCommonConf = *cfg
-	return
+	return cfg, err
 }
 
-func parseClientCommonCfgFromCmd() (err error) {
+func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) {
+	cfg = config.GetDefaultClientConf()
+
 	strs := strings.Split(serverAddr, ":")
 	if len(strs) < 2 {
 		err = fmt.Errorf("invalid server_addr")
 		return
 	}
 	if strs[0] != "" {
-		g.GlbClientCfg.ServerAddr = strs[0]
+		cfg.ServerAddr = strs[0]
 	}
-	g.GlbClientCfg.ServerPort, err = strconv.Atoi(strs[1])
+	cfg.ServerPort, err = strconv.Atoi(strs[1])
 	if err != nil {
 		err = fmt.Errorf("invalid server_addr")
 		return
 	}
 
-	g.GlbClientCfg.User = user
-	g.GlbClientCfg.Protocol = protocol
-	g.GlbClientCfg.Token = token
-	g.GlbClientCfg.LogLevel = logLevel
-	g.GlbClientCfg.LogFile = logFile
-	g.GlbClientCfg.LogMaxDays = int64(logMaxDays)
+	cfg.User = user
+	cfg.Protocol = protocol
+	cfg.Token = token
+	cfg.LogLevel = logLevel
+	cfg.LogFile = logFile
+	cfg.LogMaxDays = int64(logMaxDays)
 	if logFile == "console" {
-		g.GlbClientCfg.LogWay = "console"
+		cfg.LogWay = "console"
 	} else {
-		g.GlbClientCfg.LogWay = "file"
+		cfg.LogWay = "file"
 	}
-	return nil
+	cfg.DisableLogColor = disableLogColor
+
+	return
 }
 
 func runClient(cfgFilePath string) (err error) {
@@ -174,26 +177,27 @@ func runClient(cfgFilePath string) (err error) {
 	if err != nil {
 		return
 	}
-	g.GlbClientCfg.CfgFile = cfgFilePath
 
-	err = parseClientCommonCfg(CfgFileTypeIni, content)
+	cfg, err := parseClientCommonCfg(CfgFileTypeIni, content)
 	if err != nil {
 		return
 	}
 
-	pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(g.GlbClientCfg.User, content, g.GlbClientCfg.Start)
+	pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(cfg.User, content, cfg.Start)
 	if err != nil {
 		return err
 	}
 
-	err = startService(pxyCfgs, visitorCfgs)
+	err = startService(cfg, pxyCfgs, visitorCfgs, cfgFilePath)
 	return
 }
 
-func startService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) (err error) {
-	log.InitLog(g.GlbClientCfg.LogWay, g.GlbClientCfg.LogFile, g.GlbClientCfg.LogLevel, g.GlbClientCfg.LogMaxDays)
-	if g.GlbClientCfg.DnsServer != "" {
-		s := g.GlbClientCfg.DnsServer
+func startService(cfg config.ClientCommonConf, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf, cfgFile string) (err error) {
+	log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel,
+		cfg.LogMaxDays, cfg.DisableLogColor)
+
+	if cfg.DnsServer != "" {
+		s := cfg.DnsServer
 		if !strings.Contains(s, ":") {
 			s += ":53"
 		}
@@ -205,19 +209,19 @@ func startService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]co
 			},
 		}
 	}
-	svr, errRet := client.NewService(pxyCfgs, visitorCfgs)
+	svr, errRet := client.NewService(cfg, pxyCfgs, visitorCfgs, cfgFile)
 	if errRet != nil {
 		err = errRet
 		return
 	}
 
 	// Capture the exit signal if we use kcp.
-	if g.GlbClientCfg.Protocol == "kcp" {
+	if cfg.Protocol == "kcp" {
 		go handleSignal(svr)
 	}
 
 	err = svr.Run()
-	if g.GlbClientCfg.Protocol == "kcp" {
+	if cfg.Protocol == "kcp" {
 		<-kcpDoneCh
 	}
 	return
diff --git a/cmd/frpc/sub/status.go b/cmd/frpc/sub/status.go
index 8f55ce13..783ec29f 100644
--- a/cmd/frpc/sub/status.go
+++ b/cmd/frpc/sub/status.go
@@ -27,7 +27,6 @@ import (
 	"github.com/spf13/cobra"
 
 	"github.com/fatedier/frp/client"
-	"github.com/fatedier/frp/g"
 	"github.com/fatedier/frp/models/config"
 )
 
@@ -45,13 +44,13 @@ var statusCmd = &cobra.Command{
 			os.Exit(1)
 		}
 
-		err = parseClientCommonCfg(CfgFileTypeIni, iniContent)
+		clientCfg, err := parseClientCommonCfg(CfgFileTypeIni, iniContent)
 		if err != nil {
 			fmt.Println(err)
 			os.Exit(1)
 		}
 
-		err = status()
+		err = status(clientCfg)
 		if err != nil {
 			fmt.Printf("frpc get status error: %v\n", err)
 			os.Exit(1)
@@ -60,19 +59,19 @@ var statusCmd = &cobra.Command{
 	},
 }
 
-func status() error {
-	if g.GlbClientCfg.AdminPort == 0 {
+func status(clientCfg config.ClientCommonConf) error {
+	if clientCfg.AdminPort == 0 {
 		return fmt.Errorf("admin_port shoud be set if you want to get proxy status")
 	}
 
 	req, err := http.NewRequest("GET", "http://"+
-		g.GlbClientCfg.AdminAddr+":"+fmt.Sprintf("%d", g.GlbClientCfg.AdminPort)+"/api/status", nil)
+		clientCfg.AdminAddr+":"+fmt.Sprintf("%d", clientCfg.AdminPort)+"/api/status", nil)
 	if err != nil {
 		return err
 	}
 
-	authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(g.GlbClientCfg.AdminUser+":"+
-		g.GlbClientCfg.AdminPwd))
+	authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(clientCfg.AdminUser+":"+
+		clientCfg.AdminPwd))
 
 	req.Header.Add("Authorization", authStr)
 	resp, err := http.DefaultClient.Do(req)
diff --git a/cmd/frpc/sub/stcp.go b/cmd/frpc/sub/stcp.go
index c91040ee..1a04fbf3 100644
--- a/cmd/frpc/sub/stcp.go
+++ b/cmd/frpc/sub/stcp.go
@@ -32,6 +32,7 @@ func init() {
 	stcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
 	stcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
 	stcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
+	stcpCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
 
 	stcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
 	stcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
@@ -51,7 +52,7 @@ var stcpCmd = &cobra.Command{
 	Use:   "stcp",
 	Short: "Run frpc with a single stcp proxy",
 	RunE: func(cmd *cobra.Command, args []string) error {
-		err := parseClientCommonCfg(CfgFileTypeCmd, "")
+		clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
 		if err != nil {
 			fmt.Println(err)
 			os.Exit(1)
@@ -103,7 +104,7 @@ var stcpCmd = &cobra.Command{
 			os.Exit(1)
 		}
 
-		err = startService(proxyConfs, visitorConfs)
+		err = startService(clientCfg, proxyConfs, visitorConfs, "")
 		if err != nil {
 			fmt.Println(err)
 			os.Exit(1)
diff --git a/cmd/frpc/sub/tcp.go b/cmd/frpc/sub/tcp.go
index 154db151..c2ccd2b8 100644
--- a/cmd/frpc/sub/tcp.go
+++ b/cmd/frpc/sub/tcp.go
@@ -32,6 +32,7 @@ func init() {
 	tcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
 	tcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
 	tcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
+	tcpCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
 
 	tcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
 	tcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
@@ -47,7 +48,7 @@ var tcpCmd = &cobra.Command{
 	Use:   "tcp",
 	Short: "Run frpc with a single tcp proxy",
 	RunE: func(cmd *cobra.Command, args []string) error {
-		err := parseClientCommonCfg(CfgFileTypeCmd, "")
+		clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
 		if err != nil {
 			fmt.Println(err)
 			os.Exit(1)
@@ -75,7 +76,7 @@ var tcpCmd = &cobra.Command{
 		proxyConfs := map[string]config.ProxyConf{
 			cfg.ProxyName: cfg,
 		}
-		err = startService(proxyConfs, nil)
+		err = startService(clientCfg, proxyConfs, nil, "")
 		if err != nil {
 			fmt.Println(err)
 			os.Exit(1)
diff --git a/cmd/frpc/sub/udp.go b/cmd/frpc/sub/udp.go
index 3572e416..0d73c763 100644
--- a/cmd/frpc/sub/udp.go
+++ b/cmd/frpc/sub/udp.go
@@ -32,6 +32,7 @@ func init() {
 	udpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
 	udpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
 	udpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
+	udpCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
 
 	udpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
 	udpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip")
@@ -47,7 +48,7 @@ var udpCmd = &cobra.Command{
 	Use:   "udp",
 	Short: "Run frpc with a single udp proxy",
 	RunE: func(cmd *cobra.Command, args []string) error {
-		err := parseClientCommonCfg(CfgFileTypeCmd, "")
+		clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
 		if err != nil {
 			fmt.Println(err)
 			os.Exit(1)
@@ -75,7 +76,7 @@ var udpCmd = &cobra.Command{
 		proxyConfs := map[string]config.ProxyConf{
 			cfg.ProxyName: cfg,
 		}
-		err = startService(proxyConfs, nil)
+		err = startService(clientCfg, proxyConfs, nil, "")
 		if err != nil {
 			fmt.Println(err)
 			os.Exit(1)
diff --git a/cmd/frpc/sub/xtcp.go b/cmd/frpc/sub/xtcp.go
index c15ac5ad..558294da 100644
--- a/cmd/frpc/sub/xtcp.go
+++ b/cmd/frpc/sub/xtcp.go
@@ -32,6 +32,7 @@ func init() {
 	xtcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
 	xtcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
 	xtcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
+	xtcpCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
 
 	xtcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name")
 	xtcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role")
@@ -51,7 +52,7 @@ var xtcpCmd = &cobra.Command{
 	Use:   "xtcp",
 	Short: "Run frpc with a single xtcp proxy",
 	RunE: func(cmd *cobra.Command, args []string) error {
-		err := parseClientCommonCfg(CfgFileTypeCmd, "")
+		clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "")
 		if err != nil {
 			fmt.Println(err)
 			os.Exit(1)
@@ -103,7 +104,7 @@ var xtcpCmd = &cobra.Command{
 			os.Exit(1)
 		}
 
-		err = startService(proxyConfs, visitorConfs)
+		err = startService(clientCfg, proxyConfs, visitorConfs, "")
 		if err != nil {
 			fmt.Println(err)
 			os.Exit(1)
diff --git a/cmd/frps/main.go b/cmd/frps/main.go
index 4253aed8..8264dd70 100644
--- a/cmd/frps/main.go
+++ b/cmd/frps/main.go
@@ -15,6 +15,9 @@
 package main
 
 import (
+	"math/rand"
+	"time"
+
 	"github.com/fatedier/golib/crypto"
 
 	_ "github.com/fatedier/frp/assets/frps/statik"
@@ -22,6 +25,7 @@ import (
 
 func main() {
 	crypto.DefaultSalt = "frp"
+	rand.Seed(time.Now().UnixNano())
 
 	Execute()
 }
diff --git a/cmd/frps/root.go b/cmd/frps/root.go
index 4dcd8896..fd6cdbde 100644
--- a/cmd/frps/root.go
+++ b/cmd/frps/root.go
@@ -20,7 +20,6 @@ import (
 
 	"github.com/spf13/cobra"
 
-	"github.com/fatedier/frp/g"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/server"
 	"github.com/fatedier/frp/utils/log"
@@ -53,6 +52,7 @@ var (
 	logFile           string
 	logLevel          string
 	logMaxDays        int64
+	disableLogColor   bool
 	token             string
 	subDomainHost     string
 	tcpMux            bool
@@ -80,6 +80,8 @@ func init() {
 	rootCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "log file")
 	rootCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
 	rootCmd.PersistentFlags().Int64VarP(&logMaxDays, "log_max_days", "", 3, "log max days")
+	rootCmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
+
 	rootCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
 	rootCmd.PersistentFlags().StringVarP(&subDomainHost, "subdomain_host", "", "", "subdomain host")
 	rootCmd.PersistentFlags().StringVarP(&allowPorts, "allow_ports", "", "", "allow ports")
@@ -95,6 +97,7 @@ var rootCmd = &cobra.Command{
 			return nil
 		}
 
+		var cfg config.ServerCommonConf
 		var err error
 		if cfgFile != "" {
 			var content string
@@ -102,16 +105,15 @@ var rootCmd = &cobra.Command{
 			if err != nil {
 				return err
 			}
-			g.GlbServerCfg.CfgFile = cfgFile
-			err = parseServerCommonCfg(CfgFileTypeIni, content)
+			cfg, err = parseServerCommonCfg(CfgFileTypeIni, content)
 		} else {
-			err = parseServerCommonCfg(CfgFileTypeCmd, "")
+			cfg, err = parseServerCommonCfg(CfgFileTypeCmd, "")
 		}
 		if err != nil {
 			return err
 		}
 
-		err = runServer()
+		err = runServer(cfg)
 		if err != nil {
 			fmt.Println(err)
 			os.Exit(1)
@@ -126,52 +128,51 @@ func Execute() {
 	}
 }
 
-func parseServerCommonCfg(fileType int, content string) (err error) {
+func parseServerCommonCfg(fileType int, content string) (cfg config.ServerCommonConf, err error) {
 	if fileType == CfgFileTypeIni {
-		err = parseServerCommonCfgFromIni(content)
+		cfg, err = parseServerCommonCfgFromIni(content)
 	} else if fileType == CfgFileTypeCmd {
-		err = parseServerCommonCfgFromCmd()
+		cfg, err = parseServerCommonCfgFromCmd()
 	}
 	if err != nil {
 		return
 	}
 
-	err = g.GlbServerCfg.ServerCommonConf.Check()
+	err = cfg.Check()
 	if err != nil {
 		return
 	}
-
-	config.InitServerCfg(&g.GlbServerCfg.ServerCommonConf)
 	return
 }
 
-func parseServerCommonCfgFromIni(content string) (err error) {
-	cfg, err := config.UnmarshalServerConfFromIni(&g.GlbServerCfg.ServerCommonConf, content)
+func parseServerCommonCfgFromIni(content string) (config.ServerCommonConf, error) {
+	cfg, err := config.UnmarshalServerConfFromIni(content)
 	if err != nil {
-		return err
+		return config.ServerCommonConf{}, err
 	}
-	g.GlbServerCfg.ServerCommonConf = *cfg
-	return
+	return cfg, nil
 }
 
-func parseServerCommonCfgFromCmd() (err error) {
-	g.GlbServerCfg.BindAddr = bindAddr
-	g.GlbServerCfg.BindPort = bindPort
-	g.GlbServerCfg.BindUdpPort = bindUdpPort
-	g.GlbServerCfg.KcpBindPort = kcpBindPort
-	g.GlbServerCfg.ProxyBindAddr = proxyBindAddr
-	g.GlbServerCfg.VhostHttpPort = vhostHttpPort
-	g.GlbServerCfg.VhostHttpsPort = vhostHttpsPort
-	g.GlbServerCfg.VhostHttpTimeout = vhostHttpTimeout
-	g.GlbServerCfg.DashboardAddr = dashboardAddr
-	g.GlbServerCfg.DashboardPort = dashboardPort
-	g.GlbServerCfg.DashboardUser = dashboardUser
-	g.GlbServerCfg.DashboardPwd = dashboardPwd
-	g.GlbServerCfg.LogFile = logFile
-	g.GlbServerCfg.LogLevel = logLevel
-	g.GlbServerCfg.LogMaxDays = logMaxDays
-	g.GlbServerCfg.Token = token
-	g.GlbServerCfg.SubDomainHost = subDomainHost
+func parseServerCommonCfgFromCmd() (cfg config.ServerCommonConf, err error) {
+	cfg = config.GetDefaultServerConf()
+
+	cfg.BindAddr = bindAddr
+	cfg.BindPort = bindPort
+	cfg.BindUdpPort = bindUdpPort
+	cfg.KcpBindPort = kcpBindPort
+	cfg.ProxyBindAddr = proxyBindAddr
+	cfg.VhostHttpPort = vhostHttpPort
+	cfg.VhostHttpsPort = vhostHttpsPort
+	cfg.VhostHttpTimeout = vhostHttpTimeout
+	cfg.DashboardAddr = dashboardAddr
+	cfg.DashboardPort = dashboardPort
+	cfg.DashboardUser = dashboardUser
+	cfg.DashboardPwd = dashboardPwd
+	cfg.LogFile = logFile
+	cfg.LogLevel = logLevel
+	cfg.LogMaxDays = logMaxDays
+	cfg.Token = token
+	cfg.SubDomainHost = subDomainHost
 	if len(allowPorts) > 0 {
 		// e.g. 1000-2000,2001,2002,3000-4000
 		ports, errRet := util.ParseRangeNumbers(allowPorts)
@@ -181,28 +182,27 @@ func parseServerCommonCfgFromCmd() (err error) {
 		}
 
 		for _, port := range ports {
-			g.GlbServerCfg.AllowPorts[int(port)] = struct{}{}
+			cfg.AllowPorts[int(port)] = struct{}{}
 		}
 	}
-	g.GlbServerCfg.MaxPortsPerClient = maxPortsPerClient
+	cfg.MaxPortsPerClient = maxPortsPerClient
 
 	if logFile == "console" {
-		g.GlbServerCfg.LogWay = "console"
+		cfg.LogWay = "console"
 	} else {
-		g.GlbServerCfg.LogWay = "file"
+		cfg.LogWay = "file"
 	}
+	cfg.DisableLogColor = disableLogColor
 	return
 }
 
-func runServer() (err error) {
-	log.InitLog(g.GlbServerCfg.LogWay, g.GlbServerCfg.LogFile, g.GlbServerCfg.LogLevel,
-		g.GlbServerCfg.LogMaxDays)
-	svr, err := server.NewService()
+func runServer(cfg config.ServerCommonConf) (err error) {
+	log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel, cfg.LogMaxDays, cfg.DisableLogColor)
+	svr, err := server.NewService(cfg)
 	if err != nil {
 		return err
 	}
 	log.Info("Start frps success")
-	server.ServerService = svr
 	svr.Run()
 	return
 }
diff --git a/conf/frpc_full.ini b/conf/frpc_full.ini
index 71513eea..158ff23f 100644
--- a/conf/frpc_full.ini
+++ b/conf/frpc_full.ini
@@ -18,6 +18,9 @@ log_level = info
 
 log_max_days = 3
 
+# disable log colors when log_file is console, default is false
+disable_log_color = false
+
 # for authentication
 token = 12345678
 
@@ -26,6 +29,8 @@ admin_addr = 127.0.0.1
 admin_port = 7400
 admin_user = admin
 admin_pwd = admin
+# Admin assets directory. By default, these assets are bundled with frpc.
+# assets_dir = ./static
 
 # connections will be established in advance, default value is zero
 pool_count = 5
@@ -198,6 +203,7 @@ plugin_local_addr = 127.0.0.1:80
 plugin_crt_path = ./server.crt
 plugin_key_path = ./server.key
 plugin_host_header_rewrite = 127.0.0.1
+plugin_header_X-From-Where = frp
 
 [secret_tcp]
 # If the type is secret tcp, remote_port is useless
diff --git a/conf/frps_full.ini b/conf/frps_full.ini
index a8c0e635..ed507cef 100644
--- a/conf/frps_full.ini
+++ b/conf/frps_full.ini
@@ -43,6 +43,9 @@ log_level = info
 
 log_max_days = 3
 
+# disable log colors when log_file is console, default is false
+disable_log_color = false
+
 # auth token
 token = 12345678
 
diff --git a/g/g.go b/g/g.go
deleted file mode 100644
index 3c7385f1..00000000
--- a/g/g.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package g
-
-import (
-	"github.com/fatedier/frp/models/config"
-)
-
-var (
-	GlbClientCfg *ClientCfg
-	GlbServerCfg *ServerCfg
-)
-
-func init() {
-	GlbClientCfg = &ClientCfg{
-		ClientCommonConf: *config.GetDefaultClientConf(),
-	}
-	GlbServerCfg = &ServerCfg{
-		ServerCommonConf: *config.GetDefaultServerConf(),
-	}
-}
-
-type ClientCfg struct {
-	config.ClientCommonConf
-
-	CfgFile       string
-	ServerUdpPort int // this is configured by login response from frps
-}
-
-type ServerCfg struct {
-	config.ServerCommonConf
-
-	CfgFile string
-}
diff --git a/go.sum b/go.sum
index 3eeac454..5ddda4ef 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,6 @@
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb h1:wCrNShQidLmvVWn/0PikGmpdP0vtQmnvyRg3ZBEhczw=
 github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb/go.mod h1:wx3gB6dbIfBRcucp94PI9Bt3I0F2c/MyNEWuhzpWiwk=
@@ -27,6 +28,7 @@ github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc h1:lNOt1SMsgHX
 github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc/go.mod h1:6/gX3+E/IYGa0wMORlSMla999awQFdbaeQCHjSMKIzY=
 github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/rakyll/statik v0.1.1 h1:fCLHsIMajHqD5RKigbFXpvX3dN7c80Pm12+NCrI3kvg=
 github.com/rakyll/statik v0.1.1/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs=
diff --git a/models/config/client_common.go b/models/config/client_common.go
index 1cd9ffc5..fe87e08d 100644
--- a/models/config/client_common.go
+++ b/models/config/client_common.go
@@ -23,34 +23,103 @@ import (
 	ini "github.com/vaughan0/go-ini"
 )
 
-// client common config
+// ClientCommonConf contains information for a client service. It is
+// recommended to use GetDefaultClientConf instead of creating this object
+// directly, so that all unspecified fields have reasonable default values.
 type ClientCommonConf struct {
-	ServerAddr        string              `json:"server_addr"`
-	ServerPort        int                 `json:"server_port"`
-	HttpProxy         string              `json:"http_proxy"`
-	LogFile           string              `json:"log_file"`
-	LogWay            string              `json:"log_way"`
-	LogLevel          string              `json:"log_level"`
-	LogMaxDays        int64               `json:"log_max_days"`
-	Token             string              `json:"token"`
-	AdminAddr         string              `json:"admin_addr"`
-	AdminPort         int                 `json:"admin_port"`
-	AdminUser         string              `json:"admin_user"`
-	AdminPwd          string              `json:"admin_pwd"`
-	PoolCount         int                 `json:"pool_count"`
-	TcpMux            bool                `json:"tcp_mux"`
-	User              string              `json:"user"`
-	DnsServer         string              `json:"dns_server"`
-	LoginFailExit     bool                `json:"login_fail_exit"`
-	Start             map[string]struct{} `json:"start"`
-	Protocol          string              `json:"protocol"`
-	TLSEnable         bool                `json:"tls_enable"`
-	HeartBeatInterval int64               `json:"heartbeat_interval"`
-	HeartBeatTimeout  int64               `json:"heartbeat_timeout"`
+	// ServerAddr specifies the address of the server to connect to. By
+	// default, this value is "0.0.0.0".
+	ServerAddr string `json:"server_addr"`
+	// ServerPort specifies the port to connect to the server on. By default,
+	// this value is 7000.
+	ServerPort int `json:"server_port"`
+	// HttpProxy specifies a proxy address to connect to the server through. If
+	// this value is "", the server will be connected to directly. By default,
+	// this value is read from the "http_proxy" environment variable.
+	HttpProxy string `json:"http_proxy"`
+	// LogFile specifies a file where logs will be written to. This value will
+	// only be used if LogWay is set appropriately. By default, this value is
+	// "console".
+	LogFile string `json:"log_file"`
+	// LogWay specifies the way logging is managed. Valid values are "console"
+	// or "file". If "console" is used, logs will be printed to stdout. If
+	// "file" is used, logs will be printed to LogFile. By default, this value
+	// is "console".
+	LogWay string `json:"log_way"`
+	// LogLevel specifies the minimum log level. Valid values are "trace",
+	// "debug", "info", "warn", and "error". By default, this value is "info".
+	LogLevel string `json:"log_level"`
+	// LogMaxDays specifies the maximum number of days to store log information
+	// before deletion. This is only used if LogWay == "file". By default, this
+	// value is 0.
+	LogMaxDays int64 `json:"log_max_days"`
+	// DisableLogColor disables log colors when LogWay == "console" when set to
+	// true. By default, this value is false.
+	DisableLogColor bool `json:"disable_log_color"`
+	// Token specifies the authorization token used to create keys to be sent
+	// to the server. The server must have a matching token for authorization
+	// to succeed.  By default, this value is "".
+	Token string `json:"token"`
+	// AdminAddr specifies the address that the admin server binds to. By
+	// default, this value is "127.0.0.1".
+	AdminAddr string `json:"admin_addr"`
+	// AdminPort specifies the port for the admin server to listen on. If this
+	// value is 0, the admin server will not be started. By default, this value
+	// is 0.
+	AdminPort int `json:"admin_port"`
+	// AdminUser specifies the username that the admin server will use for
+	// login. By default, this value is "admin".
+	AdminUser string `json:"admin_user"`
+	// AdminPwd specifies the password that the admin server will use for
+	// login. By default, this value is "admin".
+	AdminPwd string `json:"admin_pwd"`
+	// AssetsDir specifies the local directory that the admin server will load
+	// resources from. If this value is "", assets will be loaded from the
+	// bundled executable using statik. By default, this value is "".
+	AssetsDir string `json:"assets_dir"`
+	// PoolCount specifies the number of connections the client will make to
+	// the server in advance. By default, this value is 0.
+	PoolCount int `json:"pool_count"`
+	// TcpMux toggles TCP stream multiplexing. This allows multiple requests
+	// from a client to share a single TCP connection. If this value is true,
+	// the server must have TCP multiplexing enabled as well. By default, this
+	// value is true.
+	TcpMux bool `json:"tcp_mux"`
+	// User specifies a prefix for proxy names to distinguish them from other
+	// clients. If this value is not "", proxy names will automatically be
+	// changed to "{user}.{proxy_name}". By default, this value is "".
+	User string `json:"user"`
+	// DnsServer specifies a DNS server address for FRPC to use. If this value
+	// is "", the default DNS will be used. By default, this value is "".
+	DnsServer string `json:"dns_server"`
+	// LoginFailExit controls whether or not the client should exit after a
+	// failed login attempt. If false, the client will retry until a login
+	// attempt succeeds. By default, this value is true.
+	LoginFailExit bool `json:"login_fail_exit"`
+	// Start specifies a set of enabled proxies by name. If this set is empty,
+	// all supplied proxies are enabled. By default, this value is an empty
+	// set.
+	Start map[string]struct{} `json:"start"`
+	// Protocol specifies the protocol to use when interacting with the server.
+	// Valid values are "tcp", "kcp", and "websocket". By default, this value
+	// is "tcp".
+	Protocol string `json:"protocol"`
+	// TLSEnable specifies whether or not TLS should be used when communicating
+	// with the server.
+	TLSEnable bool `json:"tls_enable"`
+	// HeartBeatInterval specifies at what interval heartbeats are sent to the
+	// server, in seconds. It is not recommended to change this value. By
+	// default, this value is 30.
+	HeartBeatInterval int64 `json:"heartbeat_interval"`
+	// HeartBeatTimeout specifies the maximum allowed heartbeat response delay
+	// before the connection is terminated, in seconds. It is not recommended
+	// to change this value. By default, this value is 90.
+	HeartBeatTimeout int64 `json:"heartbeat_timeout"`
 }
 
-func GetDefaultClientConf() *ClientCommonConf {
-	return &ClientCommonConf{
+// GetDefaultClientConf returns a client configuration with default values.
+func GetDefaultClientConf() ClientCommonConf {
+	return ClientCommonConf{
 		ServerAddr:        "0.0.0.0",
 		ServerPort:        7000,
 		HttpProxy:         os.Getenv("http_proxy"),
@@ -58,11 +127,13 @@ func GetDefaultClientConf() *ClientCommonConf {
 		LogWay:            "console",
 		LogLevel:          "info",
 		LogMaxDays:        3,
+		DisableLogColor:   false,
 		Token:             "",
 		AdminAddr:         "127.0.0.1",
 		AdminPort:         0,
 		AdminUser:         "",
 		AdminPwd:          "",
+		AssetsDir:         "",
 		PoolCount:         1,
 		TcpMux:            true,
 		User:              "",
@@ -76,16 +147,12 @@ func GetDefaultClientConf() *ClientCommonConf {
 	}
 }
 
-func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (cfg *ClientCommonConf, err error) {
-	cfg = defaultCfg
-	if cfg == nil {
-		cfg = GetDefaultClientConf()
-	}
+func UnmarshalClientConfFromIni(content string) (cfg ClientCommonConf, err error) {
+	cfg = GetDefaultClientConf()
 
 	conf, err := ini.Load(strings.NewReader(content))
 	if err != nil {
-		err = fmt.Errorf("parse ini conf file error: %v", err)
-		return nil, err
+		return ClientCommonConf{}, fmt.Errorf("parse ini conf file error: %v", err)
 	}
 
 	var (
@@ -106,6 +173,10 @@ func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (c
 		cfg.ServerPort = int(v)
 	}
 
+	if tmpStr, ok = conf.Get("common", "disable_log_color"); ok && tmpStr == "true" {
+		cfg.DisableLogColor = true
+	}
+
 	if tmpStr, ok = conf.Get("common", "http_proxy"); ok {
 		cfg.HttpProxy = tmpStr
 	}
@@ -154,6 +225,10 @@ func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (c
 		cfg.AdminPwd = tmpStr
 	}
 
+	if tmpStr, ok = conf.Get("common", "assets_dir"); ok {
+		cfg.AssetsDir = tmpStr
+	}
+
 	if tmpStr, ok = conf.Get("common", "pool_count"); ok {
 		if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil {
 			cfg.PoolCount = int(v)
diff --git a/models/config/proxy.go b/models/config/proxy.go
index a27416f9..9ab1ef88 100644
--- a/models/config/proxy.go
+++ b/models/config/proxy.go
@@ -58,11 +58,11 @@ type ProxyConf interface {
 	UnmarshalFromIni(prefix string, name string, conf ini.Section) error
 	MarshalToMsg(pMsg *msg.NewProxy)
 	CheckForCli() error
-	CheckForSvr() error
+	CheckForSvr(serverCfg ServerCommonConf) error
 	Compare(conf ProxyConf) bool
 }
 
-func NewProxyConfFromMsg(pMsg *msg.NewProxy) (cfg ProxyConf, err error) {
+func NewProxyConfFromMsg(pMsg *msg.NewProxy, serverCfg ServerCommonConf) (cfg ProxyConf, err error) {
 	if pMsg.ProxyType == "" {
 		pMsg.ProxyType = consts.TcpProxy
 	}
@@ -73,7 +73,7 @@ func NewProxyConfFromMsg(pMsg *msg.NewProxy) (cfg ProxyConf, err error) {
 		return
 	}
 	cfg.UnmarshalFromMsg(pMsg)
-	err = cfg.CheckForSvr()
+	err = cfg.CheckForSvr(serverCfg)
 	return
 }
 
@@ -97,17 +97,33 @@ func NewProxyConfFromIni(prefix string, name string, section ini.Section) (cfg P
 	return
 }
 
-// BaseProxy info
+// BaseProxyConf provides configuration info that is common to all proxy types.
 type BaseProxyConf struct {
+	// ProxyName is the name of this proxy.
 	ProxyName string `json:"proxy_name"`
+	// ProxyType specifies the type of this proxy. Valid values include "tcp",
+	// "udp", "http", "https", "stcp", and "xtcp". By default, this value is
+	// "tcp".
 	ProxyType string `json:"proxy_type"`
 
-	UseEncryption  bool   `json:"use_encryption"`
-	UseCompression bool   `json:"use_compression"`
-	Group          string `json:"group"`
-	GroupKey       string `json:"group_key"`
+	// UseEncryption controls whether or not communication with the server will
+	// be encrypted. Encryption is done using the tokens supplied in the server
+	// and client configuration. By default, this value is false.
+	UseEncryption bool `json:"use_encryption"`
+	// UseCompression controls whether or not communication with the server
+	// will be compressed. By default, this value is false.
+	UseCompression bool `json:"use_compression"`
+	// Group specifies which group the proxy is a part of. The server will use
+	// this information to load balance proxies in the same group. If the value
+	// is "", this proxy will not be in a group. By default, this value is "".
+	Group string `json:"group"`
+	// GroupKey specifies a group key, which should be the same among proxies
+	// of the same group. By default, this value is "".
+	GroupKey string `json:"group_key"`
 
-	// only used for client
+	// ProxyProtocolVersion specifies which protocol version to use. Valid
+	// values include "v1", "v2", and "". If the value is "", a protocol
+	// version will be automatically selected. By default, this value is "".
 	ProxyProtocolVersion string `json:"proxy_protocol_version"`
 	LocalSvrConf
 	HealthCheckConf
@@ -308,21 +324,21 @@ func (cfg *DomainConf) checkForCli() (err error) {
 	return
 }
 
-func (cfg *DomainConf) checkForSvr() (err error) {
+func (cfg *DomainConf) checkForSvr(serverCfg ServerCommonConf) (err error) {
 	if err = cfg.check(); err != nil {
 		return
 	}
 
 	for _, domain := range cfg.CustomDomains {
-		if subDomainHost != "" && len(strings.Split(subDomainHost, ".")) < len(strings.Split(domain, ".")) {
-			if strings.Contains(domain, subDomainHost) {
-				return fmt.Errorf("custom domain [%s] should not belong to subdomain_host [%s]", domain, subDomainHost)
+		if serverCfg.SubDomainHost != "" && len(strings.Split(serverCfg.SubDomainHost, ".")) < len(strings.Split(domain, ".")) {
+			if strings.Contains(domain, serverCfg.SubDomainHost) {
+				return fmt.Errorf("custom domain [%s] should not belong to subdomain_host [%s]", domain, serverCfg.SubDomainHost)
 			}
 		}
 	}
 
 	if cfg.SubDomain != "" {
-		if subDomainHost == "" {
+		if serverCfg.SubDomainHost == "" {
 			return fmt.Errorf("subdomain is not supported because this feature is not enabled in remote frps")
 		}
 		if strings.Contains(cfg.SubDomain, ".") || strings.Contains(cfg.SubDomain, "*") {
@@ -332,12 +348,20 @@ func (cfg *DomainConf) checkForSvr() (err error) {
 	return
 }
 
-// Local service info
+// LocalSvrConf configures what location the client will proxy to, or what
+// plugin will be used.
 type LocalSvrConf struct {
-	LocalIp   string `json:"local_ip"`
-	LocalPort int    `json:"local_port"`
+	// LocalIp specifies the IP address or host name to proxy to.
+	LocalIp string `json:"local_ip"`
+	// LocalPort specifies the port to proxy to.
+	LocalPort int `json:"local_port"`
 
-	Plugin       string            `json:"plugin"`
+	// Plugin specifies what plugin should be used for proxying. If this value
+	// is set, the LocalIp and LocalPort values will be ignored. By default,
+	// this value is "".
+	Plugin string `json:"plugin"`
+	// PluginParams specify parameters to be passed to the plugin, if one is
+	// being used. By default, this value is an empty map.
 	PluginParams map[string]string `json:"plugin_params"`
 }
 
@@ -399,15 +423,35 @@ func (cfg *LocalSvrConf) checkForCli() (err error) {
 	return
 }
 
-// Health check info
+// HealthCheckConf configures health checking. This can be useful for load
+// balancing purposes to detect and remove proxies to failing services.
 type HealthCheckConf struct {
-	HealthCheckType      string `json:"health_check_type"` // tcp | http
-	HealthCheckTimeoutS  int    `json:"health_check_timeout_s"`
-	HealthCheckMaxFailed int    `json:"health_check_max_failed"`
-	HealthCheckIntervalS int    `json:"health_check_interval_s"`
-	HealthCheckUrl       string `json:"health_check_url"`
-
-	// local_ip + local_port
+	// HealthCheckType specifies what protocol to use for health checking.
+	// Valid values include "tcp", "http", and "". If this value is "", health
+	// checking will not be performed. By default, this value is "".
+	//
+	// If the type is "tcp", a connection will be attempted to the target
+	// server. If a connection cannot be established, the health check fails.
+	//
+	// If the type is "http", a GET request will be made to the endpoint
+	// specified by HealthCheckUrl. If the response is not a 200, the health
+	// check fails.
+	HealthCheckType string `json:"health_check_type"` // tcp | http
+	// HealthCheckTimeoutS specifies the number of seconds to wait for a health
+	// check attempt to connect. If the timeout is reached, this counts as a
+	// health check failure. By default, this value is 3.
+	HealthCheckTimeoutS int `json:"health_check_timeout_s"`
+	// HealthCheckMaxFailed specifies the number of allowed failures before the
+	// proxy is stopped. By default, this value is 1.
+	HealthCheckMaxFailed int `json:"health_check_max_failed"`
+	// HealthCheckIntervalS specifies the time in seconds between health
+	// checks. By default, this value is 10.
+	HealthCheckIntervalS int `json:"health_check_interval_s"`
+	// HealthCheckUrl specifies the address to send health checks to if the
+	// health check type is "http".
+	HealthCheckUrl string `json:"health_check_url"`
+	// HealthCheckAddr specifies the address to connect to if the health check
+	// type is "tcp".
 	HealthCheckAddr string `json:"-"`
 }
 
@@ -504,7 +548,7 @@ func (cfg *TcpProxyConf) CheckForCli() (err error) {
 	return
 }
 
-func (cfg *TcpProxyConf) CheckForSvr() error { return nil }
+func (cfg *TcpProxyConf) CheckForSvr(serverCfg ServerCommonConf) error { return nil }
 
 // UDP
 type UdpProxyConf struct {
@@ -552,7 +596,7 @@ func (cfg *UdpProxyConf) CheckForCli() (err error) {
 	return
 }
 
-func (cfg *UdpProxyConf) CheckForSvr() error { return nil }
+func (cfg *UdpProxyConf) CheckForSvr(serverCfg ServerCommonConf) error { return nil }
 
 // HTTP
 type HttpProxyConf struct {
@@ -657,11 +701,11 @@ func (cfg *HttpProxyConf) CheckForCli() (err error) {
 	return
 }
 
-func (cfg *HttpProxyConf) CheckForSvr() (err error) {
-	if vhostHttpPort == 0 {
+func (cfg *HttpProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
+	if serverCfg.VhostHttpPort == 0 {
 		return fmt.Errorf("type [http] not support when vhost_http_port is not set")
 	}
-	if err = cfg.DomainConf.checkForSvr(); err != nil {
+	if err = cfg.DomainConf.checkForSvr(serverCfg); err != nil {
 		err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
 		return
 	}
@@ -717,11 +761,11 @@ func (cfg *HttpsProxyConf) CheckForCli() (err error) {
 	return
 }
 
-func (cfg *HttpsProxyConf) CheckForSvr() (err error) {
-	if vhostHttpsPort == 0 {
+func (cfg *HttpsProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
+	if serverCfg.VhostHttpsPort == 0 {
 		return fmt.Errorf("type [https] not support when vhost_https_port is not set")
 	}
-	if err = cfg.DomainConf.checkForSvr(); err != nil {
+	if err = cfg.DomainConf.checkForSvr(serverCfg); err != nil {
 		err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err)
 		return
 	}
@@ -790,7 +834,7 @@ func (cfg *StcpProxyConf) CheckForCli() (err error) {
 	return
 }
 
-func (cfg *StcpProxyConf) CheckForSvr() (err error) {
+func (cfg *StcpProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
 	return
 }
 
@@ -857,7 +901,7 @@ func (cfg *XtcpProxyConf) CheckForCli() (err error) {
 	return
 }
 
-func (cfg *XtcpProxyConf) CheckForSvr() (err error) {
+func (cfg *XtcpProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) {
 	return
 }
 
diff --git a/models/config/server_common.go b/models/config/server_common.go
index 1e54bdd8..a190a61a 100644
--- a/models/config/server_common.go
+++ b/models/config/server_common.go
@@ -24,62 +24,122 @@ import (
 	"github.com/fatedier/frp/utils/util"
 )
 
-var (
-	// server global configure used for generate proxy conf used in frps
-	proxyBindAddr  string
-	subDomainHost  string
-	vhostHttpPort  int
-	vhostHttpsPort int
-)
-
-func InitServerCfg(cfg *ServerCommonConf) {
-	proxyBindAddr = cfg.ProxyBindAddr
-	subDomainHost = cfg.SubDomainHost
-	vhostHttpPort = cfg.VhostHttpPort
-	vhostHttpsPort = cfg.VhostHttpsPort
-}
-
-// common config
+// ServerCommonConf contains information for a server service. It is
+// recommended to use GetDefaultServerConf instead of creating this object
+// directly, so that all unspecified fields have reasonable default values.
 type ServerCommonConf struct {
-	BindAddr      string `json:"bind_addr"`
-	BindPort      int    `json:"bind_port"`
-	BindUdpPort   int    `json:"bind_udp_port"`
-	KcpBindPort   int    `json:"kcp_bind_port"`
+	// BindAddr specifies the address that the server binds to. By default,
+	// this value is "0.0.0.0".
+	BindAddr string `json:"bind_addr"`
+	// BindPort specifies the port that the server listens on. By default, this
+	// value is 7000.
+	BindPort int `json:"bind_port"`
+	// BindUdpPort specifies the UDP port that the server listens on. If this
+	// value is 0, the server will not listen for UDP connections. By default,
+	// this value is 0
+	BindUdpPort int `json:"bind_udp_port"`
+	// BindKcpPort specifies the KCP port that the server listens on. If this
+	// value is 0, the server will not listen for KCP connections. By default,
+	// this value is 0.
+	KcpBindPort int `json:"kcp_bind_port"`
+	// ProxyBindAddr specifies the address that the proxy binds to. This value
+	// may be the same as BindAddr. By default, this value is "0.0.0.0".
 	ProxyBindAddr string `json:"proxy_bind_addr"`
 
-	// If VhostHttpPort equals 0, don't listen a public port for http protocol.
+	// VhostHttpPort specifies the port that the server listens for HTTP Vhost
+	// requests. If this value is 0, the server will not listen for HTTP
+	// requests. By default, this value is 0.
 	VhostHttpPort int `json:"vhost_http_port"`
 
-	// if VhostHttpsPort equals 0, don't listen a public port for https protocol
+	// VhostHttpsPort specifies the port that the server listens for HTTPS
+	// Vhost requests. If this value is 0, the server will not listen for HTTPS
+	// requests. By default, this value is 0.
 	VhostHttpsPort int `json:"vhost_https_port"`
 
+	// VhostHttpTimeout specifies the response header timeout for the Vhost
+	// HTTP server, in seconds. By default, this value is 60.
 	VhostHttpTimeout int64 `json:"vhost_http_timeout"`
 
+	// DashboardAddr specifies the address that the dashboard binds to. By
+	// default, this value is "0.0.0.0".
 	DashboardAddr string `json:"dashboard_addr"`
 
-	// if DashboardPort equals 0, dashboard is not available
-	DashboardPort int    `json:"dashboard_port"`
+	// DashboardPort specifies the port that the dashboard listens on. If this
+	// value is 0, the dashboard will not be started. By default, this value is
+	// 0.
+	DashboardPort int `json:"dashboard_port"`
+	// DashboardUser specifies the username that the dashboard will use for
+	// login. By default, this value is "admin".
 	DashboardUser string `json:"dashboard_user"`
-	DashboardPwd  string `json:"dashboard_pwd"`
-	AssetsDir     string `json:"asserts_dir"`
-	LogFile       string `json:"log_file"`
-	LogWay        string `json:"log_way"` // console or file
-	LogLevel      string `json:"log_level"`
-	LogMaxDays    int64  `json:"log_max_days"`
-	Token         string `json:"token"`
+	// DashboardUser specifies the password that the dashboard will use for
+	// login. By default, this value is "admin".
+	DashboardPwd string `json:"dashboard_pwd"`
+	// AssetsDir specifies the local directory that the dashboard will load
+	// resources from. If this value is "", assets will be loaded from the
+	// bundled executable using statik. By default, this value is "".
+	AssetsDir string `json:"asserts_dir"`
+	// LogFile specifies a file where logs will be written to. This value will
+	// only be used if LogWay is set appropriately. By default, this value is
+	// "console".
+	LogFile string `json:"log_file"`
+	// LogWay specifies the way logging is managed. Valid values are "console"
+	// or "file". If "console" is used, logs will be printed to stdout. If
+	// "file" is used, logs will be printed to LogFile. By default, this value
+	// is "console".
+	LogWay string `json:"log_way"`
+	// LogLevel specifies the minimum log level. Valid values are "trace",
+	// "debug", "info", "warn", and "error". By default, this value is "info".
+	LogLevel string `json:"log_level"`
+	// LogMaxDays specifies the maximum number of days to store log information
+	// before deletion. This is only used if LogWay == "file". By default, this
+	// value is 0.
+	LogMaxDays int64 `json:"log_max_days"`
+	// DisableLogColor disables log colors when LogWay == "console" when set to
+	// true. By default, this value is false.
+	DisableLogColor bool `json:"disable_log_color"`
+	// Token specifies the authorization token used to authenticate keys
+	// received from clients. Clients must have a matching token to be
+	// authorized to use the server. By default, this value is "".
+	Token string `json:"token"`
+	// SubDomainHost specifies the domain that will be attached to sub-domains
+	// requested by the client when using Vhost proxying. For example, if this
+	// value is set to "frps.com" and the client requested the subdomain
+	// "test", the resulting URL would be "test.frps.com". By default, this
+	// value is "".
 	SubDomainHost string `json:"subdomain_host"`
-	TcpMux        bool   `json:"tcp_mux"`
+	// TcpMux toggles TCP stream multiplexing. This allows multiple requests
+	// from a client to share a single TCP connection. By default, this value
+	// is true.
+	TcpMux bool `json:"tcp_mux"`
+	// Custom404Page specifies a path to a custom 404 page to display. If this
+	// value is "", a default page will be displayed. By default, this value is
+	// "".
 	Custom404Page string `json:"custom_404_page"`
 
-	AllowPorts        map[int]struct{}
-	MaxPoolCount      int64 `json:"max_pool_count"`
+	// AllowPorts specifies a set of ports that clients are able to proxy to.
+	// If the length of this value is 0, all ports are allowed. By default,
+	// this value is an empty set.
+	AllowPorts map[int]struct{}
+	// MaxPoolCount specifies the maximum pool size for each proxy. By default,
+	// this value is 5.
+	MaxPoolCount int64 `json:"max_pool_count"`
+	// MaxPortsPerClient specifies the maximum number of ports a single client
+	// may proxy to. If this value is 0, no limit will be applied. By default,
+	// this value is 0.
 	MaxPortsPerClient int64 `json:"max_ports_per_client"`
-	HeartBeatTimeout  int64 `json:"heart_beat_timeout"`
-	UserConnTimeout   int64 `json:"user_conn_timeout"`
+	// HeartBeatTimeout specifies the maximum time to wait for a heartbeat
+	// before terminating the connection. It is not recommended to change this
+	// value. By default, this value is 90.
+	HeartBeatTimeout int64 `json:"heart_beat_timeout"`
+	// UserConnTimeout specifies the maximum time to wait for a work
+	// connection. By default, this value is 10.
+	UserConnTimeout int64 `json:"user_conn_timeout"`
 }
 
-func GetDefaultServerConf() *ServerCommonConf {
-	return &ServerCommonConf{
+// GetDefaultServerConf returns a server configuration with reasonable
+// defaults.
+func GetDefaultServerConf() ServerCommonConf {
+	return ServerCommonConf{
 		BindAddr:          "0.0.0.0",
 		BindPort:          7000,
 		BindUdpPort:       0,
@@ -97,6 +157,7 @@ func GetDefaultServerConf() *ServerCommonConf {
 		LogWay:            "console",
 		LogLevel:          "info",
 		LogMaxDays:        3,
+		DisableLogColor:   false,
 		Token:             "",
 		SubDomainHost:     "",
 		TcpMux:            true,
@@ -109,16 +170,15 @@ func GetDefaultServerConf() *ServerCommonConf {
 	}
 }
 
-func UnmarshalServerConfFromIni(defaultCfg *ServerCommonConf, content string) (cfg *ServerCommonConf, err error) {
-	cfg = defaultCfg
-	if cfg == nil {
-		cfg = GetDefaultServerConf()
-	}
+// UnmarshalServerConfFromIni parses the contents of a server configuration ini
+// file and returns the resulting server configuration.
+func UnmarshalServerConfFromIni(content string) (cfg ServerCommonConf, err error) {
+	cfg = GetDefaultServerConf()
 
 	conf, err := ini.Load(strings.NewReader(content))
 	if err != nil {
 		err = fmt.Errorf("parse ini conf file error: %v", err)
-		return nil, err
+		return ServerCommonConf{}, err
 	}
 
 	var (
@@ -244,6 +304,10 @@ func UnmarshalServerConfFromIni(defaultCfg *ServerCommonConf, content string) (c
 		}
 	}
 
+	if tmpStr, ok = conf.Get("common", "disable_log_color"); ok && tmpStr == "true" {
+		cfg.DisableLogColor = true
+	}
+
 	cfg.Token, _ = conf.Get("common", "token")
 
 	if allowPortsStr, ok := conf.Get("common", "allow_ports"); ok {
diff --git a/models/plugin/https2http.go b/models/plugin/https2http.go
index 6e84ad62..f840ebde 100644
--- a/models/plugin/https2http.go
+++ b/models/plugin/https2http.go
@@ -20,6 +20,7 @@ import (
 	"io"
 	"net/http"
 	"net/http/httputil"
+	"strings"
 
 	frpNet "github.com/fatedier/frp/utils/net"
 )
@@ -35,6 +36,7 @@ type HTTPS2HTTPPlugin struct {
 	keyPath           string
 	hostHeaderRewrite string
 	localAddr         string
+	headers           map[string]string
 
 	l *Listener
 	s *http.Server
@@ -45,6 +47,15 @@ func NewHTTPS2HTTPPlugin(params map[string]string) (Plugin, error) {
 	keyPath := params["plugin_key_path"]
 	localAddr := params["plugin_local_addr"]
 	hostHeaderRewrite := params["plugin_host_header_rewrite"]
+	headers := make(map[string]string)
+	for k, v := range params {
+		if !strings.HasPrefix(k, "plugin_header_") {
+			continue
+		}
+		if k = strings.TrimPrefix(k, "plugin_header_"); k != "" {
+			headers[k] = v
+		}
+	}
 
 	if crtPath == "" {
 		return nil, fmt.Errorf("plugin_crt_path is required")
@@ -63,6 +74,7 @@ func NewHTTPS2HTTPPlugin(params map[string]string) (Plugin, error) {
 		keyPath:           keyPath,
 		localAddr:         localAddr,
 		hostHeaderRewrite: hostHeaderRewrite,
+		headers:           headers,
 		l:                 listener,
 	}
 
@@ -73,6 +85,9 @@ func NewHTTPS2HTTPPlugin(params map[string]string) (Plugin, error) {
 			if p.hostHeaderRewrite != "" {
 				req.Host = p.hostHeaderRewrite
 			}
+			for k, v := range p.headers {
+				req.Header.Set(k, v)
+			}
 		},
 	}
 
diff --git a/server/control.go b/server/control.go
index 8374ab20..bb802a75 100644
--- a/server/control.go
+++ b/server/control.go
@@ -21,7 +21,6 @@ import (
 	"sync"
 	"time"
 
-	"github.com/fatedier/frp/g"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/consts"
 	frpErr "github.com/fatedier/frp/models/errors"
@@ -129,11 +128,19 @@ type Control struct {
 	allShutdown     *shutdown.Shutdown
 
 	mu sync.RWMutex
+
+	// Server configuration information
+	serverCfg config.ServerCommonConf
 }
 
 func NewControl(rc *controller.ResourceController, pxyManager *proxy.ProxyManager,
-	statsCollector stats.Collector, ctlConn net.Conn, loginMsg *msg.Login) *Control {
+	statsCollector stats.Collector, ctlConn net.Conn, loginMsg *msg.Login,
+	serverCfg config.ServerCommonConf) *Control {
 
+	poolCount := loginMsg.PoolCount
+	if poolCount > int(serverCfg.MaxPoolCount) {
+		poolCount = int(serverCfg.MaxPoolCount)
+	}
 	return &Control{
 		rc:              rc,
 		pxyManager:      pxyManager,
@@ -142,9 +149,9 @@ func NewControl(rc *controller.ResourceController, pxyManager *proxy.ProxyManage
 		loginMsg:        loginMsg,
 		sendCh:          make(chan msg.Message, 10),
 		readCh:          make(chan msg.Message, 10),
-		workConnCh:      make(chan net.Conn, loginMsg.PoolCount+10),
+		workConnCh:      make(chan net.Conn, poolCount+10),
 		proxies:         make(map[string]proxy.Proxy),
-		poolCount:       loginMsg.PoolCount,
+		poolCount:       poolCount,
 		portsUsedNum:    0,
 		lastPing:        time.Now(),
 		runId:           loginMsg.RunId,
@@ -153,6 +160,7 @@ func NewControl(rc *controller.ResourceController, pxyManager *proxy.ProxyManage
 		writerShutdown:  shutdown.New(),
 		managerShutdown: shutdown.New(),
 		allShutdown:     shutdown.New(),
+		serverCfg:       serverCfg,
 	}
 }
 
@@ -161,7 +169,7 @@ func (ctl *Control) Start() {
 	loginRespMsg := &msg.LoginResp{
 		Version:       version.Full(),
 		RunId:         ctl.runId,
-		ServerUdpPort: g.GlbServerCfg.BindUdpPort,
+		ServerUdpPort: ctl.serverCfg.BindUdpPort,
 		Error:         "",
 	}
 	msg.WriteMsg(ctl.conn, loginRespMsg)
@@ -232,7 +240,7 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
 				return
 			}
 
-		case <-time.After(time.Duration(g.GlbServerCfg.UserConnTimeout) * time.Second):
+		case <-time.After(time.Duration(ctl.serverCfg.UserConnTimeout) * time.Second):
 			err = fmt.Errorf("timeout trying to get work connection")
 			ctl.conn.Warn("%v", err)
 			return
@@ -263,7 +271,7 @@ func (ctl *Control) writer() {
 	defer ctl.allShutdown.Start()
 	defer ctl.writerShutdown.Done()
 
-	encWriter, err := crypto.NewWriter(ctl.conn, []byte(g.GlbServerCfg.Token))
+	encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.serverCfg.Token))
 	if err != nil {
 		ctl.conn.Error("crypto new writer error: %v", err)
 		ctl.allShutdown.Start()
@@ -293,7 +301,7 @@ func (ctl *Control) reader() {
 	defer ctl.allShutdown.Start()
 	defer ctl.readerShutdown.Done()
 
-	encReader := crypto.NewReader(ctl.conn, []byte(g.GlbServerCfg.Token))
+	encReader := crypto.NewReader(ctl.conn, []byte(ctl.serverCfg.Token))
 	for {
 		if m, err := msg.ReadMsg(encReader); err != nil {
 			if err == io.EOF {
@@ -374,7 +382,7 @@ func (ctl *Control) manager() {
 	for {
 		select {
 		case <-heartbeat.C:
-			if time.Since(ctl.lastPing) > time.Duration(g.GlbServerCfg.HeartBeatTimeout)*time.Second {
+			if time.Since(ctl.lastPing) > time.Duration(ctl.serverCfg.HeartBeatTimeout)*time.Second {
 				ctl.conn.Warn("heartbeat timeout")
 				return
 			}
@@ -417,22 +425,22 @@ func (ctl *Control) manager() {
 func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err error) {
 	var pxyConf config.ProxyConf
 	// Load configures from NewProxy message and check.
-	pxyConf, err = config.NewProxyConfFromMsg(pxyMsg)
+	pxyConf, err = config.NewProxyConfFromMsg(pxyMsg, ctl.serverCfg)
 	if err != nil {
 		return
 	}
 
 	// NewProxy will return a interface Proxy.
 	// In fact it create different proxies by different proxy type, we just call run() here.
-	pxy, err := proxy.NewProxy(ctl.runId, ctl.rc, ctl.statsCollector, ctl.poolCount, ctl.GetWorkConn, pxyConf)
+	pxy, err := proxy.NewProxy(ctl.runId, ctl.rc, ctl.statsCollector, ctl.poolCount, ctl.GetWorkConn, pxyConf, ctl.serverCfg)
 	if err != nil {
 		return remoteAddr, err
 	}
 
 	// Check ports used number in each client
-	if g.GlbServerCfg.MaxPortsPerClient > 0 {
+	if ctl.serverCfg.MaxPortsPerClient > 0 {
 		ctl.mu.Lock()
-		if ctl.portsUsedNum+pxy.GetUsedPortsNum() > int(g.GlbServerCfg.MaxPortsPerClient) {
+		if ctl.portsUsedNum+pxy.GetUsedPortsNum() > int(ctl.serverCfg.MaxPortsPerClient) {
 			ctl.mu.Unlock()
 			err = fmt.Errorf("exceed the max_ports_per_client")
 			return
@@ -478,7 +486,7 @@ func (ctl *Control) CloseProxy(closeMsg *msg.CloseProxy) (err error) {
 		return
 	}
 
-	if g.GlbServerCfg.MaxPortsPerClient > 0 {
+	if ctl.serverCfg.MaxPortsPerClient > 0 {
 		ctl.portsUsedNum = ctl.portsUsedNum - pxy.GetUsedPortsNum()
 	}
 	pxy.Close()
diff --git a/server/dashboard.go b/server/dashboard.go
index d682aeb9..0a65dbb2 100644
--- a/server/dashboard.go
+++ b/server/dashboard.go
@@ -21,7 +21,6 @@ import (
 	"time"
 
 	"github.com/fatedier/frp/assets"
-	"github.com/fatedier/frp/g"
 	frpNet "github.com/fatedier/frp/utils/net"
 
 	"github.com/gorilla/mux"
@@ -36,7 +35,7 @@ func (svr *Service) RunDashboardServer(addr string, port int) (err error) {
 	// url router
 	router := mux.NewRouter()
 
-	user, passwd := g.GlbServerCfg.DashboardUser, g.GlbServerCfg.DashboardPwd
+	user, passwd := svr.cfg.DashboardUser, svr.cfg.DashboardPwd
 	router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware)
 
 	// api, see dashboard_api.go
diff --git a/server/dashboard_api.go b/server/dashboard_api.go
index dcea7e61..30ee9a68 100644
--- a/server/dashboard_api.go
+++ b/server/dashboard_api.go
@@ -18,7 +18,6 @@ import (
 	"encoding/json"
 	"net/http"
 
-	"github.com/fatedier/frp/g"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/consts"
 	"github.com/fatedier/frp/utils/log"
@@ -63,19 +62,18 @@ func (svr *Service) ApiServerInfo(w http.ResponseWriter, r *http.Request) {
 	}()
 
 	log.Info("Http request: [%s]", r.URL.Path)
-	cfg := &g.GlbServerCfg.ServerCommonConf
 	serverStats := svr.statsCollector.GetServer()
 	svrResp := ServerInfoResp{
 		Version:           version.Full(),
-		BindPort:          cfg.BindPort,
-		BindUdpPort:       cfg.BindUdpPort,
-		VhostHttpPort:     cfg.VhostHttpPort,
-		VhostHttpsPort:    cfg.VhostHttpsPort,
-		KcpBindPort:       cfg.KcpBindPort,
-		SubdomainHost:     cfg.SubDomainHost,
-		MaxPoolCount:      cfg.MaxPoolCount,
-		MaxPortsPerClient: cfg.MaxPortsPerClient,
-		HeartBeatTimeout:  cfg.HeartBeatTimeout,
+		BindPort:          svr.cfg.BindPort,
+		BindUdpPort:       svr.cfg.BindUdpPort,
+		VhostHttpPort:     svr.cfg.VhostHttpPort,
+		VhostHttpsPort:    svr.cfg.VhostHttpsPort,
+		KcpBindPort:       svr.cfg.KcpBindPort,
+		SubdomainHost:     svr.cfg.SubDomainHost,
+		MaxPoolCount:      svr.cfg.MaxPoolCount,
+		MaxPortsPerClient: svr.cfg.MaxPortsPerClient,
+		HeartBeatTimeout:  svr.cfg.HeartBeatTimeout,
 
 		TotalTrafficIn:  serverStats.TotalTrafficIn,
 		TotalTrafficOut: serverStats.TotalTrafficOut,
diff --git a/server/proxy/http.go b/server/proxy/http.go
index 1fa8765e..5bd3139d 100644
--- a/server/proxy/http.go
+++ b/server/proxy/http.go
@@ -19,7 +19,6 @@ import (
 	"net"
 	"strings"
 
-	"github.com/fatedier/frp/g"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/server/stats"
 	frpNet "github.com/fatedier/frp/utils/net"
@@ -88,13 +87,13 @@ func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
 					pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation)
 				})
 			}
-			addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(g.GlbServerCfg.VhostHttpPort)))
+			addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(pxy.serverCfg.VhostHttpPort)))
 			pxy.Info("http proxy listen for host [%s] location [%s] group [%s]", routeConfig.Domain, routeConfig.Location, pxy.cfg.Group)
 		}
 	}
 
 	if pxy.cfg.SubDomain != "" {
-		routeConfig.Domain = pxy.cfg.SubDomain + "." + g.GlbServerCfg.SubDomainHost
+		routeConfig.Domain = pxy.cfg.SubDomain + "." + pxy.serverCfg.SubDomainHost
 		for _, location := range locations {
 			routeConfig.Location = location
 			tmpDomain := routeConfig.Domain
@@ -119,7 +118,7 @@ func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
 					pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation)
 				})
 			}
-			addrs = append(addrs, util.CanonicalAddr(tmpDomain, g.GlbServerCfg.VhostHttpPort))
+			addrs = append(addrs, util.CanonicalAddr(tmpDomain, pxy.serverCfg.VhostHttpPort))
 
 			pxy.Info("http proxy listen for host [%s] location [%s] group [%s]", routeConfig.Domain, routeConfig.Location, pxy.cfg.Group)
 		}
@@ -147,7 +146,7 @@ func (pxy *HttpProxy) GetRealConn(remoteAddr string) (workConn frpNet.Conn, err
 
 	var rwc io.ReadWriteCloser = tmpConn
 	if pxy.cfg.UseEncryption {
-		rwc, err = frpIo.WithEncryption(rwc, []byte(g.GlbServerCfg.Token))
+		rwc, err = frpIo.WithEncryption(rwc, []byte(pxy.serverCfg.Token))
 		if err != nil {
 			pxy.Error("create encryption stream error: %v", err)
 			return
diff --git a/server/proxy/https.go b/server/proxy/https.go
index cb5ce928..87840421 100644
--- a/server/proxy/https.go
+++ b/server/proxy/https.go
@@ -17,7 +17,6 @@ package proxy
 import (
 	"strings"
 
-	"github.com/fatedier/frp/g"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/utils/util"
 	"github.com/fatedier/frp/utils/vhost"
@@ -51,11 +50,11 @@ func (pxy *HttpsProxy) Run() (remoteAddr string, err error) {
 		l.AddLogPrefix(pxy.name)
 		pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
 		pxy.listeners = append(pxy.listeners, l)
-		addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, g.GlbServerCfg.VhostHttpsPort))
+		addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, pxy.serverCfg.VhostHttpsPort))
 	}
 
 	if pxy.cfg.SubDomain != "" {
-		routeConfig.Domain = pxy.cfg.SubDomain + "." + g.GlbServerCfg.SubDomainHost
+		routeConfig.Domain = pxy.cfg.SubDomain + "." + pxy.serverCfg.SubDomainHost
 		l, errRet := pxy.rc.VhostHttpsMuxer.Listen(routeConfig)
 		if errRet != nil {
 			err = errRet
@@ -64,7 +63,7 @@ func (pxy *HttpsProxy) Run() (remoteAddr string, err error) {
 		l.AddLogPrefix(pxy.name)
 		pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
 		pxy.listeners = append(pxy.listeners, l)
-		addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(g.GlbServerCfg.VhostHttpsPort)))
+		addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(pxy.serverCfg.VhostHttpsPort)))
 	}
 
 	pxy.startListenHandler(pxy, HandleUserTcpConnection)
diff --git a/server/proxy/proxy.go b/server/proxy/proxy.go
index 1a9a28e1..627b0e15 100644
--- a/server/proxy/proxy.go
+++ b/server/proxy/proxy.go
@@ -21,7 +21,6 @@ import (
 	"strconv"
 	"sync"
 
-	"github.com/fatedier/frp/g"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/msg"
 	"github.com/fatedier/frp/server/controller"
@@ -52,6 +51,7 @@ type BaseProxy struct {
 	usedPortsNum   int
 	poolCount      int
 	getWorkConnFn  GetWorkConnFn
+	serverCfg      config.ServerCommonConf
 
 	mu sync.RWMutex
 	log.Logger
@@ -126,7 +126,7 @@ func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn frpNet.Co
 // startListenHandler start a goroutine handler for each listener.
 // p: p will just be passed to handler(Proxy, frpNet.Conn).
 // handler: each proxy type can set different handler function to deal with connections accepted from listeners.
-func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, frpNet.Conn, stats.Collector)) {
+func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, frpNet.Conn, stats.Collector, config.ServerCommonConf)) {
 	for _, listener := range pxy.listeners {
 		go func(l frpNet.Listener) {
 			for {
@@ -138,14 +138,14 @@ func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, frpNet.Con
 					return
 				}
 				pxy.Debug("get a user connection [%s]", c.RemoteAddr().String())
-				go handler(p, c, pxy.statsCollector)
+				go handler(p, c, pxy.statsCollector, pxy.serverCfg)
 			}
 		}(listener)
 	}
 }
 
 func NewProxy(runId string, rc *controller.ResourceController, statsCollector stats.Collector, poolCount int,
-	getWorkConnFn GetWorkConnFn, pxyConf config.ProxyConf) (pxy Proxy, err error) {
+	getWorkConnFn GetWorkConnFn, pxyConf config.ProxyConf, serverCfg config.ServerCommonConf) (pxy Proxy, err error) {
 
 	basePxy := BaseProxy{
 		name:           pxyConf.GetBaseInfo().ProxyName,
@@ -155,6 +155,7 @@ func NewProxy(runId string, rc *controller.ResourceController, statsCollector st
 		poolCount:      poolCount,
 		getWorkConnFn:  getWorkConnFn,
 		Logger:         log.NewPrefixLogger(runId),
+		serverCfg:      serverCfg,
 	}
 	switch cfg := pxyConf.(type) {
 	case *config.TcpProxyConf:
@@ -198,7 +199,7 @@ func NewProxy(runId string, rc *controller.ResourceController, statsCollector st
 
 // HandleUserTcpConnection is used for incoming tcp user connections.
 // It can be used for tcp, http, https type.
-func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn, statsCollector stats.Collector) {
+func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn, statsCollector stats.Collector, serverCfg config.ServerCommonConf) {
 	defer userConn.Close()
 
 	// try all connections from the pool
@@ -211,7 +212,7 @@ func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn, statsCollector sta
 	var local io.ReadWriteCloser = workConn
 	cfg := pxy.GetConf().GetBaseInfo()
 	if cfg.UseEncryption {
-		local, err = frpIo.WithEncryption(local, []byte(g.GlbServerCfg.Token))
+		local, err = frpIo.WithEncryption(local, []byte(serverCfg.Token))
 		if err != nil {
 			pxy.Error("create encryption stream error: %v", err)
 			return
diff --git a/server/proxy/tcp.go b/server/proxy/tcp.go
index e00e3a0e..388531a6 100644
--- a/server/proxy/tcp.go
+++ b/server/proxy/tcp.go
@@ -17,7 +17,6 @@ package proxy
 import (
 	"fmt"
 
-	"github.com/fatedier/frp/g"
 	"github.com/fatedier/frp/models/config"
 	frpNet "github.com/fatedier/frp/utils/net"
 )
@@ -31,7 +30,7 @@ type TcpProxy struct {
 
 func (pxy *TcpProxy) Run() (remoteAddr string, err error) {
 	if pxy.cfg.Group != "" {
-		l, realPort, errRet := pxy.rc.TcpGroupCtl.Listen(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, g.GlbServerCfg.ProxyBindAddr, pxy.cfg.RemotePort)
+		l, realPort, errRet := pxy.rc.TcpGroupCtl.Listen(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, pxy.serverCfg.ProxyBindAddr, pxy.cfg.RemotePort)
 		if errRet != nil {
 			err = errRet
 			return
@@ -56,7 +55,7 @@ func (pxy *TcpProxy) Run() (remoteAddr string, err error) {
 				pxy.rc.TcpPortManager.Release(pxy.realPort)
 			}
 		}()
-		listener, errRet := frpNet.ListenTcp(g.GlbServerCfg.ProxyBindAddr, pxy.realPort)
+		listener, errRet := frpNet.ListenTcp(pxy.serverCfg.ProxyBindAddr, pxy.realPort)
 		if errRet != nil {
 			err = errRet
 			return
diff --git a/server/proxy/udp.go b/server/proxy/udp.go
index b5dd5fbb..453c7b56 100644
--- a/server/proxy/udp.go
+++ b/server/proxy/udp.go
@@ -20,7 +20,6 @@ import (
 	"net"
 	"time"
 
-	"github.com/fatedier/frp/g"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/msg"
 	"github.com/fatedier/frp/models/proto/udp"
@@ -67,7 +66,7 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
 
 	remoteAddr = fmt.Sprintf(":%d", pxy.realPort)
 	pxy.cfg.RemotePort = pxy.realPort
-	addr, errRet := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", g.GlbServerCfg.ProxyBindAddr, pxy.realPort))
+	addr, errRet := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pxy.serverCfg.ProxyBindAddr, pxy.realPort))
 	if errRet != nil {
 		err = errRet
 		return
diff --git a/server/service.go b/server/service.go
index e3a1d117..6d561016 100644
--- a/server/service.go
+++ b/server/service.go
@@ -29,7 +29,7 @@ import (
 	"time"
 
 	"github.com/fatedier/frp/assets"
-	"github.com/fatedier/frp/g"
+	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/msg"
 	"github.com/fatedier/frp/models/nathole"
 	"github.com/fatedier/frp/server/controller"
@@ -51,8 +51,6 @@ const (
 	connReadTimeout time.Duration = 10 * time.Second
 )
 
-var ServerService *Service
-
 // Server service
 type Service struct {
 	// Dispatch connections to different handlers listen on same port
@@ -86,10 +84,11 @@ type Service struct {
 	statsCollector stats.Collector
 
 	tlsConfig *tls.Config
+
+	cfg config.ServerCommonConf
 }
 
-func NewService() (svr *Service, err error) {
-	cfg := &g.GlbServerCfg.ServerCommonConf
+func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
 	svr = &Service{
 		ctlManager: NewControlManager(),
 		pxyManager: proxy.NewProxyManager(),
@@ -100,6 +99,7 @@ func NewService() (svr *Service, err error) {
 		},
 		httpVhostRouter: vhost.NewVhostRouters(),
 		tlsConfig:       generateTLSConfig(),
+		cfg:             cfg,
 	}
 
 	// Init group controller
@@ -108,13 +108,6 @@ func NewService() (svr *Service, err error) {
 	// Init HTTP group controller
 	svr.rc.HTTPGroupCtl = group.NewHTTPGroupController(svr.httpVhostRouter)
 
-	// Init assets
-	err = assets.Load(cfg.AssetsDir)
-	if err != nil {
-		err = fmt.Errorf("Load assets error: %v", err)
-		return
-	}
-
 	// Init 404 not found page
 	vhost.NotFoundPagePath = cfg.Custom404Page
 
@@ -231,6 +224,13 @@ func NewService() (svr *Service, err error) {
 	var statsEnable bool
 	// Create dashboard web server.
 	if cfg.DashboardPort > 0 {
+		// Init dashboard assets
+		err = assets.Load(cfg.AssetsDir)
+		if err != nil {
+			err = fmt.Errorf("Load assets error: %v", err)
+			return
+		}
+
 		err = svr.RunDashboardServer(cfg.DashboardAddr, cfg.DashboardPort)
 		if err != nil {
 			err = fmt.Errorf("Create dashboard web server error, %v", err)
@@ -248,7 +248,7 @@ func (svr *Service) Run() {
 	if svr.rc.NatHoleController != nil {
 		go svr.rc.NatHoleController.Run()
 	}
-	if g.GlbServerCfg.KcpBindPort > 0 {
+	if svr.cfg.KcpBindPort > 0 {
 		go svr.HandleListener(svr.kcpListener)
 	}
 
@@ -324,7 +324,7 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
 				}
 			}
 
-			if g.GlbServerCfg.TcpMux {
+			if svr.cfg.TcpMux {
 				fmuxCfg := fmux.DefaultConfig()
 				fmuxCfg.KeepAliveInterval = 20 * time.Second
 				fmuxCfg.LogOutput = ioutil.Discard
@@ -363,7 +363,7 @@ func (svr *Service) RegisterControl(ctlConn frpNet.Conn, loginMsg *msg.Login) (e
 	}
 
 	// Check auth.
-	if util.GetAuthKey(g.GlbServerCfg.Token, loginMsg.Timestamp) != loginMsg.PrivilegeKey {
+	if util.GetAuthKey(svr.cfg.Token, loginMsg.Timestamp) != loginMsg.PrivilegeKey {
 		err = fmt.Errorf("authorization failed")
 		return
 	}
@@ -377,7 +377,7 @@ func (svr *Service) RegisterControl(ctlConn frpNet.Conn, loginMsg *msg.Login) (e
 		}
 	}
 
-	ctl := NewControl(svr.rc, svr.pxyManager, svr.statsCollector, ctlConn, loginMsg)
+	ctl := NewControl(svr.rc, svr.pxyManager, svr.statsCollector, ctlConn, loginMsg, svr.cfg)
 
 	if oldCtl := svr.ctlManager.Add(loginMsg.RunId, ctl); oldCtl != nil {
 		oldCtl.allShutdown.WaitDone()
diff --git a/tests/ci/health/health_test.go b/tests/ci/health/health_test.go
index 99ee22bd..f19c3750 100644
--- a/tests/ci/health/health_test.go
+++ b/tests/ci/health/health_test.go
@@ -67,6 +67,20 @@ custom_domains = test2.com
 health_check_type = http
 health_check_interval_s = 1
 health_check_url = /health
+
+[http3]
+type = http
+local_port = 15005
+custom_domains = test.balancing.com
+group = test-balancing
+group_key = 123
+
+[http4]
+type = http
+local_port = 15006
+custom_domains = test.balancing.com
+group = test-balancing
+group_key = 123
 `
 
 func TestHealthCheck(t *testing.T) {
@@ -124,6 +138,22 @@ func TestHealthCheck(t *testing.T) {
 		defer httpSvc2.Stop()
 	}
 
+	httpSvc3 := mock.NewHttpServer(15005, func(w http.ResponseWriter, r *http.Request) {
+		w.Write([]byte("http3"))
+	})
+	err = httpSvc3.Start()
+	if assert.NoError(err) {
+		defer httpSvc3.Stop()
+	}
+
+	httpSvc4 := mock.NewHttpServer(15006, func(w http.ResponseWriter, r *http.Request) {
+		w.Write([]byte("http4"))
+	})
+	err = httpSvc4.Start()
+	if assert.NoError(err) {
+		defer httpSvc4.Stop()
+	}
+
 	time.Sleep(200 * time.Millisecond)
 
 	// ****** start frps and frpc ******
@@ -244,4 +274,20 @@ func TestHealthCheck(t *testing.T) {
 	assert.NoError(err)
 	assert.Equal(200, code)
 	assert.Equal("http2", body)
+
+	// ****** load balancing type http ******
+	result = make([]string, 0)
+
+	code, body, _, err = util.SendHttpMsg("GET", "http://127.0.0.1:14000/xxx", "test.balancing.com", nil, "")
+	assert.NoError(err)
+	assert.Equal(200, code)
+	result = append(result, body)
+
+	code, body, _, err = util.SendHttpMsg("GET", "http://127.0.0.1:14000/xxx", "test.balancing.com", nil, "")
+	assert.NoError(err)
+	assert.Equal(200, code)
+	result = append(result, body)
+
+	assert.Contains(result, "http3")
+	assert.Contains(result, "http4")
 }
diff --git a/utils/log/log.go b/utils/log/log.go
index c0ce7f12..1a9033bf 100644
--- a/utils/log/log.go
+++ b/utils/log/log.go
@@ -29,16 +29,20 @@ func init() {
 	Log.SetLogFuncCallDepth(Log.GetLogFuncCallDepth() + 1)
 }
 
-func InitLog(logWay string, logFile string, logLevel string, maxdays int64) {
-	SetLogFile(logWay, logFile, maxdays)
+func InitLog(logWay string, logFile string, logLevel string, maxdays int64, disableLogColor bool) {
+	SetLogFile(logWay, logFile, maxdays, disableLogColor)
 	SetLogLevel(logLevel)
 }
 
 // SetLogFile to configure log params
 // logWay: file or console
-func SetLogFile(logWay string, logFile string, maxdays int64) {
+func SetLogFile(logWay string, logFile string, maxdays int64, disableLogColor bool) {
 	if logWay == "console" {
-		Log.SetLogger("console", "")
+		params := ""
+		if disableLogColor {
+			params = fmt.Sprintf(`{"color": false}`)
+		}
+		Log.SetLogger("console", params)
 	} else {
 		params := fmt.Sprintf(`{"filename": "%s", "maxdays": %d}`, logFile, maxdays)
 		Log.SetLogger("file", params)
diff --git a/utils/version/version.go b/utils/version/version.go
index a4cb6c09..7a73de8f 100644
--- a/utils/version/version.go
+++ b/utils/version/version.go
@@ -19,7 +19,7 @@ import (
 	"strings"
 )
 
-var version string = "0.28.2"
+var version string = "0.29.0"
 
 func Full() string {
 	return version