diff --git a/client/proxy.go b/client/proxy.go
index a4f99c64..26c9a66e 100644
--- a/client/proxy.go
+++ b/client/proxy.go
@@ -272,6 +272,12 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn) {
 	}
 	clientConn.SetReadDeadline(time.Time{})
 	clientConn.Close()
+
+	if natHoleRespMsg.Error != "" {
+		pxy.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error)
+		return
+	}
+
 	pxy.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr)
 
 	// Send sid to visitor udp address.
diff --git a/client/visitor.go b/client/visitor.go
index d07eb8e7..3587d497 100644
--- a/client/visitor.go
+++ b/client/visitor.go
@@ -235,6 +235,11 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
 	visitorConn.SetReadDeadline(time.Time{})
 	pool.PutBuf(buf)
 
+	if natHoleRespMsg.Error != "" {
+		sv.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error)
+		return
+	}
+
 	sv.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr)
 
 	// Close visitorConn, so we can use it's local address.
diff --git a/models/msg/msg.go b/models/msg/msg.go
index 93214fcf..fd3e656b 100644
--- a/models/msg/msg.go
+++ b/models/msg/msg.go
@@ -163,6 +163,7 @@ type NatHoleResp struct {
 	Sid         string `json:"sid"`
 	VisitorAddr string `json:"visitor_addr"`
 	ClientAddr  string `json:"client_addr"`
+	Error       string `json:"error"`
 }
 
 type NatHoleSid struct {
diff --git a/server/nathole.go b/server/nathole.go
index 0ad0c48f..cc8339f4 100644
--- a/server/nathole.go
+++ b/server/nathole.go
@@ -108,12 +108,16 @@ func (nc *NatHoleController) HandleVisitor(m *msg.NatHoleVisitor, raddr *net.UDP
 	clientCfg, ok := nc.clientCfgs[m.ProxyName]
 	if !ok {
 		nc.mu.Unlock()
-		log.Debug("xtcp server for [%s] doesn't exist", m.ProxyName)
+		errInfo := fmt.Sprintf("xtcp server for [%s] doesn't exist", m.ProxyName)
+		log.Debug(errInfo)
+		nc.listener.WriteToUDP(nc.GenNatHoleResponse(nil, errInfo), raddr)
 		return
 	}
 	if m.SignKey != util.GetAuthKey(clientCfg.Sk, m.Timestamp) {
 		nc.mu.Unlock()
-		log.Debug("xtcp connection of [%s] auth failed", m.ProxyName)
+		errInfo := fmt.Sprintf("xtcp connection of [%s] auth failed", m.ProxyName)
+		log.Debug(errInfo)
+		nc.listener.WriteToUDP(nc.GenNatHoleResponse(nil, errInfo), raddr)
 		return
 	}
 
@@ -137,7 +141,7 @@ func (nc *NatHoleController) HandleVisitor(m *msg.NatHoleVisitor, raddr *net.UDP
 	// Wait client connections.
 	select {
 	case <-session.NotifyCh:
-		resp := nc.GenNatHoleResponse(raddr, session)
+		resp := nc.GenNatHoleResponse(session, "")
 		log.Trace("send nat hole response to visitor")
 		nc.listener.WriteToUDP(resp, raddr)
 	case <-time.After(time.Duration(NatHoleTimeout) * time.Second):
@@ -156,16 +160,27 @@ func (nc *NatHoleController) HandleClient(m *msg.NatHoleClient, raddr *net.UDPAd
 	session.ClientAddr = raddr
 	session.NotifyCh <- struct{}{}
 
-	resp := nc.GenNatHoleResponse(raddr, session)
+	resp := nc.GenNatHoleResponse(session, "")
 	log.Trace("send nat hole response to client")
 	nc.listener.WriteToUDP(resp, raddr)
 }
 
-func (nc *NatHoleController) GenNatHoleResponse(raddr *net.UDPAddr, session *NatHoleSession) []byte {
+func (nc *NatHoleController) GenNatHoleResponse(session *NatHoleSession, errInfo string) []byte {
+	var (
+		sid         string
+		visitorAddr string
+		clientAddr  string
+	)
+	if session != nil {
+		sid = session.Sid
+		visitorAddr = session.VisitorAddr.String()
+		clientAddr = session.ClientAddr.String()
+	}
 	m := &msg.NatHoleResp{
-		Sid:         session.Sid,
-		VisitorAddr: session.VisitorAddr.String(),
-		ClientAddr:  session.ClientAddr.String(),
+		Sid:         sid,
+		VisitorAddr: visitorAddr,
+		ClientAddr:  clientAddr,
+		Error:       errInfo,
 	}
 	b := bytes.NewBuffer(nil)
 	err := msg.WriteMsg(b, m)