diff --git a/cmd/rdpgw/protocol/errors.go b/cmd/rdpgw/protocol/errors.go
new file mode 100644
index 0000000000000000000000000000000000000000..e7c8eed7207d6aa68cca79891875e23177e7d661
--- /dev/null
+++ b/cmd/rdpgw/protocol/errors.go
@@ -0,0 +1,124 @@
+package protocol
+
+const (
+	ERROR_NO                                       = 0x0000000
+	ERROR_CLIENT_DISCONNECT                        = 0x0000001
+	ERROR_CLIENT_LOGOFF                            = 0x0000002
+	ERROR_NETWORK_DISCONNECT                       = 0x0000003
+	ERROR_NOT_FOUND                                = 0x0000104
+	ERROR_NO_MEM                                   = 0x0000106
+	ERROR_CONNECT_TIMEOUT                          = 0x0000108
+	ERROR_SMARTCARD_SERVICE                        = 0x000010A
+	ERROR_UNAVAILABLE                              = 0x0000204
+	ERROR_SMARTCARD_READER                         = 0x000020A
+	ERROR_NETWORK                                  = 0x0000304
+	ERROR_SMARTCART_NOCARD                         = 0x000030A
+	ERROR_SECURITY                                 = 0x0000406
+	ERROR_INVALID_NAME                             = 0x0000408
+	ERROR_SMARTCARD_SUBSYSTEM                      = 0x000040A
+	ERROR_GENERIC                                  = 0x0000704
+	ERROR_CONSOLE_EXIST                            = 0x0000708
+	ERROR_LICENSING_PROTOCOL                       = 0x0000808
+	ERROR_NETWORK_GENERIC                          = 0x0000904
+	ERROR_SECURITY_UNEXPECTED_CERTIFICATE          = 0x0000907
+	ERROR_LICENSING_TIMEOUT                        = 0x0000908
+	ERROR_SECURITY_USER                            = 0x0000A07
+	ERROR_GENERIC_UNAVAIL                          = 0x0000B04
+	ERROR_ENCRYPTION                               = 0x0000B06
+	ERROR_SECURITY_USER_DISABLED                   = 0x0000B07
+	ERROR_SECURITY_NLA_REQUIRED                    = 0x0000B09
+	ERROR_SECURITY_USER_RESTRICTION                = 0x0000C07
+	ERROR_DECOMPRESSION                            = 0x0000C08
+	ERROR_SECURITY_USER_LOCKED_OUT                 = 0x0000D07
+	ERROR_SECURITY_USER_DIALOG_REQUIRED            = 0x0000D09
+	ERROR_SECURITY_FIPS_REQUIRED                   = 0x0000E06
+	ERROR_SECURITY_USER_EXPIRED                    = 0x0000E07
+	ERROR_GENERIC_FAILED                           = 0x0000E08
+	ERROR_SERVER_RA_UNAVAILABLE                    = 0x0000E09
+	ERROR_SECURITY_USER_PASSWORD_EXPIRED           = 0x0000F07
+	ERROR_SECURITY_USER_CREDENTIALS_NOT_SENT       = 0x0000F08
+	ERROR_SECURITY_USER_TIME_RESTRICTION           = 0x0001007
+	ERROR_LOW_VIDEO                                = 0x0001008
+	ERROR_SECURITY_USER_COMPUTER_RANGE             = 0x0001107
+	ERROR_SECURITY_USER_CHANGE_PASSWORD            = 0x0001207
+	ERROR_SECURITY_USER_LOGON_TYPE                 = 0x0001307
+	ERROR_KRB_SUB_REQUIRED                         = 0x0001407
+	ERROR_SECURITY_SERVER_INVALID_CERTIFICATE      = 0x0001B07
+	ERROR_SECURITY_SERVER_TIMESKEW                 = 0x0001D07
+	ERROR_SECURITY_SMARTCARD_LOCKEDOUT             = 0x0002207
+	ERROR_RELAUNCH_APP                             = 0x0002507
+	ERROR_UPGRADE_CLIENT                           = 0x0002604
+	ERROR_RELAUNCH_REMOTE                          = 0x2000001
+	ERROR_REMOTEAPP_UNSUPPORTED                    = 0x2000002
+	ERROR_SECURITY_USER_PASSWORD_INVALID           = 0x3000001
+	ERROR_SECURITY_CERTIFICATE_REVOKE_LIST_UNAVAIL = 0x3000002
+	ERROR_SECURITY_CERTIFICATE_INVALID             = 0x3000003
+	ERROR_SECURITY_CERTIFICATE_REVOKED             = 0x3000004
+	ERROR_SECURITY_GATEWAY_IDENTITY                = 0x3000005
+	ERROR_SECURITY_GATEWAY_SUBJECT                 = 0x3000006
+	ERROR_SECURITY_GATEWAY_EXPIRED                 = 0x3000007
+	ERROR_SECURITY_REMOTE_ERROR                    = 0x3000008
+	ERROR_GATEWAY_NETWORK_SEND                     = 0x3000009
+	ERROR_GATEWAY_NETWORK_RECEIVE                  = 0x300000A
+	ERROR_SECURITY_ALTERNATE                       = 0x300000B
+	ERROR_GATEWAY_INVALID_ADDRESS                  = 0x300000C
+	ERROR_GATEWAY_TEMP_UNAVAIL                     = 0x300000D
+	ERROR_REMOTE_CLIENT_MISSING                    = 0x300000E
+	ERROR_GATEWAY_LOW_RESOURCES                    = 0x300000F
+	ERROR_GATEWAY_CLIENT_DLL                       = 0x3000010
+	ERROR_SMARTCART_NOSERVICE                      = 0x3000011
+	ERROR_SECURITY_SMARTCARD_REMOVED               = 0x3000012
+	ERROR_SECURITY_SMARTCARD_REQUIRED              = 0x3000013
+	ERROR_SECURITY_SMARTCARD_REMOVED2              = 0x3000014
+	ERROR_SECURITY_USER_PASSWORD_INVALID2          = 0x3000015
+	ERROR_SECURITY_TRANSPORT                       = 0x3000017
+	ERROR_GATEWAY_TERMINATE                        = 0x3000018
+	ERROR_GATEWAY_ADMIN_TERMINATE                  = 0x3000019
+	ERROR_SECURITY_USER_CREDENTIALS                = 0x300001A
+	ERROR_SECURITY_GATEWAY_NOT_PERMITTED           = 0x300001B
+	ERROR_SECURITY_GATEWAY_UNAUTHORIZED            = 0x300001C
+	ERROR_SECURITY_GATEWAY_RESTRICTED              = 0x300001F
+	ERROR_SECURITY_PROXY_AUTH                      = 0x3000020
+	ERROR_SECURITY_USER_PASSWORD_MUST_CHANGE       = 0x3000021
+	ERROR_GATEWAY_MAX_REACHED                      = 0x3000022
+	ERROR_GATEWAY_UNSUPPORTED_REQUEST              = 0x3000023
+	ERROR_GATEWAY_UNSUPPORTED_CAP                  = 0x3000024
+	ERROR_GATEWAY_INCOMPAT                         = 0x3000025
+	ERROR_SECURITY_SMARTCARD_INVALID_CREDENTIALS   = 0x3000026
+	ERROR_SECURITY_NLA_INVALID                     = 0x3000027
+	ERROR_GATEWAY_NO_CERTIFICATE                   = 0x3000028
+	ERROR_GATEWAY_NOT_ALLOWED                      = 0x3000029
+	ERROR_GATEWAY_INVALID_CERTIFICATE              = 0x300002A
+	ERROR_SECURITY_GATEWAY_USER_PASSWORD_REQUIRED  = 0x300002B
+	ERROR_SECURITY_GATEWAY_SMARTCARD_REQUIRED      = 0x300002C
+	ERROR_SECURITY_SMARTCARD_UNAVAIL               = 0x300002D
+	ERROR_SECURITY_FIREWALL_NOAUTH                 = 0x300002F
+	ERROR_SECURITY_FIREWALL_AUTH                   = 0x3000030
+	ERROR_NO_INPUT                                 = 0x3000032
+	ERROR_TIMEOUT                                  = 0x3000033
+	ERROR_SECURITY_GATEWAY_COOKIE_INVALID          = 0x3000034
+	ERROR_SECURITY_GATEWAY_COOKIE_REJECTED         = 0x3000035
+	ERROR_SECURITY_GATEWAY_AUTH_METHOD             = 0x3000037
+	ERROR_SECURITY_USER_PERIOD_AUTH                = 0x3000038
+	ERROR_SECURITY_USER_PERIOD_AUTHZ               = 0x3000039
+	ERROR_SECURITY_GATEWAY_POLICY                  = 0x300003B
+	ERROR_SECURITY_SMARTCARD_CERTIFICATE           = 0x300003C
+	ERROR_LOGON_FIRST                              = 0x300003D
+	ERROR_AUTH_LOGON_FIRST                         = 0x300003E
+	ERROR_SESSION_ENDED                            = 0x300003F
+	ERROR_SESSION_ENDED_AUTH                       = 0x3000040
+	ERROR_SECURITY_GATEWAY_NAP                     = 0x3000041
+	ERROR_COOKIE_SIZE                              = 0x3000042
+	ERROR_PROXY_CONFIG                             = 0x3000044
+	ERROR_NO_PERMISSION                            = 0x3000045
+	ERROR_NO_RESOURCES                             = 0x3000046
+	ERROR_RESOURCE_ACCESS                          = 0x3000047
+	ERROR_UPGRADE_CLIENT2                          = 0x3000049
+	ERROR_SECURITY_NETWORK_HTTPS                   = 0x300004A
+	ERROR_TEMP_FAIL                                = 0x300004B
+	ERROR_SECURITY_USER_MISMATCH                   = 0x300004C
+	ERROR_AZURE_TOO_MANY                           = 0x300004D
+	ERROR_MAX_USER                                 = 0x300004E
+	ERROR_AZURE_TRIAL                              = 0x300004F
+	ERROR_AZURE_EXPIRED                            = 0x3000050
+)
diff --git a/cmd/rdpgw/protocol/gateway.go b/cmd/rdpgw/protocol/gateway.go
index 7f95857a9a9454f5f46abddb048c28caf74c1986..2cd96a57b66e56373eec7c8ed28e314db9707b71 100644
--- a/cmd/rdpgw/protocol/gateway.go
+++ b/cmd/rdpgw/protocol/gateway.go
@@ -160,6 +160,8 @@ func (g *Gateway) handleWebsocketProtocol(ctx context.Context, c *websocket.Conn
 	defer websocketConnections.Dec()
 
 	inout, _ := transport.NewWS(c)
+	defer inout.Close()
+
 	s.TransportOut = inout
 	s.TransportIn = inout
 	handler := NewServer(s, g.ServerConf)
diff --git a/cmd/rdpgw/protocol/server.go b/cmd/rdpgw/protocol/server.go
index 852fdd010d31ba1a4710fc2c48a14bc87a1cb418..7f7a70f3c2cca227cd776657be20d0d4efe0cefc 100644
--- a/cmd/rdpgw/protocol/server.go
+++ b/cmd/rdpgw/protocol/server.go
@@ -5,6 +5,7 @@ import (
 	"context"
 	"encoding/binary"
 	"errors"
+	"fmt"
 	"github.com/bolkedebruin/rdpgw/cmd/rdpgw/common"
 	"io"
 	"log"
@@ -45,7 +46,7 @@ type ServerConf struct {
 
 func NewServer(s *SessionInfo, conf *ServerConf) *Server {
 	h := &Server{
-		State:                SERVER_STATE_INITIAL,
+		State:                SERVER_STATE_INITIALIZED,
 		Session:              s,
 		RedirectFlags:        makeRedirectFlags(conf.RedirectFlags),
 		IdleTimeout:          conf.IdleTimeout,
@@ -71,12 +72,14 @@ func (s *Server) Process(ctx context.Context) error {
 		switch pt {
 		case PKT_TYPE_HANDSHAKE_REQUEST:
 			log.Printf("Client handshakeRequest from %s", common.GetClientIp(ctx))
-			if s.State != SERVER_STATE_INITIAL {
-				log.Printf("Handshake attempted while in wrong state %d != %d", s.State, SERVER_STATE_INITIAL)
-				return errors.New("wrong state")
+			if s.State != SERVER_STATE_INITIALIZED {
+				log.Printf("Handshake attempted while in wrong state %d != %d", s.State, SERVER_STATE_INITIALIZED)
+				msg := s.handshakeResponse(0x0, 0x0, ERROR_GENERIC)
+				s.Session.TransportOut.WritePacket(msg)
+				return fmt.Errorf("%x: wrong state", ERROR_GENERIC)
 			}
 			major, minor, _, _ := s.handshakeRequest(pkt) // todo check if auth matches what the handler can do
-			msg := s.handshakeResponse(major, minor)
+			msg := s.handshakeResponse(major, minor, ERROR_NO)
 			s.Session.TransportOut.WritePacket(msg)
 			s.State = SERVER_STATE_HANDSHAKE
 		case PKT_TYPE_TUNNEL_CREATE:
@@ -84,16 +87,20 @@ func (s *Server) Process(ctx context.Context) error {
 			if s.State != SERVER_STATE_HANDSHAKE {
 				log.Printf("Tunnel create attempted while in wrong state %d != %d",
 					s.State, SERVER_STATE_HANDSHAKE)
-				return errors.New("wrong state")
+				msg := s.tunnelResponse(ERROR_SECURITY_GATEWAY_COOKIE_REJECTED)
+				s.Session.TransportOut.WritePacket(msg)
+				return fmt.Errorf("%x: PAA cookie rejected, wrong state", ERROR_SECURITY_GATEWAY_COOKIE_REJECTED)
 			}
 			_, cookie := s.tunnelRequest(pkt)
 			if s.VerifyTunnelCreate != nil {
 				if ok, _ := s.VerifyTunnelCreate(ctx, cookie); !ok {
 					log.Printf("Invalid PAA cookie received from client %s", common.GetClientIp(ctx))
-					return errors.New("invalid PAA cookie")
+					msg := s.tunnelResponse(ERROR_SECURITY_GATEWAY_COOKIE_INVALID)
+					s.Session.TransportOut.WritePacket(msg)
+					return fmt.Errorf("%x: invalid PAA cookie", ERROR_SECURITY_GATEWAY_COOKIE_INVALID)
 				}
 			}
-			msg := s.tunnelResponse()
+			msg := s.tunnelResponse(ERROR_NO)
 			s.Session.TransportOut.WritePacket(msg)
 			s.State = SERVER_STATE_TUNNEL_CREATE
 		case PKT_TYPE_TUNNEL_AUTH:
@@ -101,16 +108,20 @@ func (s *Server) Process(ctx context.Context) error {
 			if s.State != SERVER_STATE_TUNNEL_CREATE {
 				log.Printf("Tunnel auth attempted while in wrong state %d != %d",
 					s.State, SERVER_STATE_TUNNEL_CREATE)
-				return errors.New("wrong state")
+				msg := s.tunnelAuthResponse(ERROR_GENERIC)
+				s.Session.TransportOut.WritePacket(msg)
+				return fmt.Errorf("%x: Tunnel auth rejected, wrong state", ERROR_GENERIC)
 			}
 			client := s.tunnelAuthRequest(pkt)
 			if s.VerifyTunnelAuthFunc != nil {
 				if ok, _ := s.VerifyTunnelAuthFunc(ctx, client); !ok {
 					log.Printf("Invalid client name: %s", client)
-					return errors.New("invalid client name")
+					msg := s.tunnelAuthResponse(ERROR_SECURITY)
+					s.Session.TransportOut.WritePacket(msg)
+					return fmt.Errorf("%x: Tunnel auth rejected, invalid client name", ERROR_SECURITY)
 				}
 			}
-			msg := s.tunnelAuthResponse()
+			msg := s.tunnelAuthResponse(ERROR_NO)
 			s.Session.TransportOut.WritePacket(msg)
 			s.State = SERVER_STATE_TUNNEL_AUTHORIZE
 		case PKT_TYPE_CHANNEL_CREATE:
@@ -118,24 +129,30 @@ func (s *Server) Process(ctx context.Context) error {
 			if s.State != SERVER_STATE_TUNNEL_AUTHORIZE {
 				log.Printf("Channel create attempted while in wrong state %d != %d",
 					s.State, SERVER_STATE_TUNNEL_AUTHORIZE)
-				return errors.New("wrong state")
+				msg := s.channelResponse(ERROR_GENERIC)
+				s.Session.TransportOut.WritePacket(msg)
+				return fmt.Errorf("%x: Channel create rejected, wrong state", ERROR_GENERIC)
 			}
 			server, port := s.channelRequest(pkt)
 			host := net.JoinHostPort(server, strconv.Itoa(int(port)))
 			if s.VerifyServerFunc != nil {
 				if ok, _ := s.VerifyServerFunc(ctx, host); !ok {
 					log.Printf("Not allowed to connect to %s by policy handler", host)
-					return errors.New("denied by security policy")
+					msg := s.channelResponse(ERROR_SECURITY_GATEWAY_POLICY)
+					s.Session.TransportOut.WritePacket(msg)
+					return fmt.Errorf("%x: denied by security policy", ERROR_SECURITY_GATEWAY_POLICY)
 				}
 			}
 			log.Printf("Establishing connection to RDP server: %s", host)
 			s.Remote, err = net.DialTimeout("tcp", host, time.Second*15)
 			if err != nil {
 				log.Printf("Error connecting to %s, %s", host, err)
+				msg := s.channelResponse(ERROR_GENERIC)
+				s.Session.TransportOut.WritePacket(msg)
 				return err
 			}
 			log.Printf("Connection established")
-			msg := s.channelResponse()
+			msg := s.channelResponse(ERROR_NO)
 			s.Session.TransportOut.WritePacket(msg)
 
 			// Make sure to start the flow from the RDP server first otherwise connections
@@ -164,7 +181,7 @@ func (s *Server) Process(ctx context.Context) error {
 				log.Printf("Channel closed while in wrong state %d != %d", s.State, SERVER_STATE_OPENED)
 				return errors.New("wrong state")
 			}
-			msg := s.channelCloseResponse()
+			msg := s.channelCloseResponse(ERROR_NO)
 			s.Session.TransportOut.WritePacket(msg)
 			//s.Session.TransportIn.Close()
 			//s.Session.TransportOut.Close()
@@ -179,7 +196,7 @@ func (s *Server) Process(ctx context.Context) error {
 // Creates a packet the is a response to a handshakeRequest request
 // HTTP_EXTENDED_AUTH_SSPI_NTLM is not supported in Linux
 // but could be in Windows. However the NTLM protocol is insecure
-func (s *Server) handshakeResponse(major byte, minor byte) []byte {
+func (s *Server) handshakeResponse(major byte, minor byte, errorCode int) []byte {
 	var caps uint16
 	if s.SmartCardAuth {
 		caps = caps | HTTP_EXTENDED_AUTH_SC
@@ -189,7 +206,7 @@ func (s *Server) handshakeResponse(major byte, minor byte) []byte {
 	}
 
 	buf := new(bytes.Buffer)
-	binary.Write(buf, binary.LittleEndian, uint32(0)) // error_code
+	binary.Write(buf, binary.LittleEndian, uint32(errorCode)) // error_code
 	buf.Write([]byte{major, minor})
 	binary.Write(buf, binary.LittleEndian, uint16(0))    // server version
 	binary.Write(buf, binary.LittleEndian, uint16(caps)) // extended auth
@@ -227,11 +244,11 @@ func (s *Server) tunnelRequest(data []byte) (caps uint32, cookie string) {
 	return
 }
 
-func (s *Server) tunnelResponse() []byte {
+func (s *Server) tunnelResponse(errorCode int) []byte {
 	buf := new(bytes.Buffer)
 
 	binary.Write(buf, binary.LittleEndian, uint16(0))                                                                    // server version
-	binary.Write(buf, binary.LittleEndian, uint32(0))                                                                    // error code
+	binary.Write(buf, binary.LittleEndian, uint32(errorCode))                                                            // error code
 	binary.Write(buf, binary.LittleEndian, uint16(HTTP_TUNNEL_RESPONSE_FIELD_TUNNEL_ID|HTTP_TUNNEL_RESPONSE_FIELD_CAPS)) // fields present
 	binary.Write(buf, binary.LittleEndian, uint16(0))                                                                    // reserved
 
@@ -255,10 +272,10 @@ func (s *Server) tunnelAuthRequest(data []byte) string {
 	return clientName
 }
 
-func (s *Server) tunnelAuthResponse() []byte {
+func (s *Server) tunnelAuthResponse(errorCode int) []byte {
 	buf := new(bytes.Buffer)
 
-	binary.Write(buf, binary.LittleEndian, uint32(0))                                                                                        // error code
+	binary.Write(buf, binary.LittleEndian, uint32(errorCode))                                                                                // error code
 	binary.Write(buf, binary.LittleEndian, uint16(HTTP_TUNNEL_AUTH_RESPONSE_FIELD_REDIR_FLAGS|HTTP_TUNNEL_AUTH_RESPONSE_FIELD_IDLE_TIMEOUT)) // fields present
 	binary.Write(buf, binary.LittleEndian, uint16(0))                                                                                        // reserved
 
@@ -295,10 +312,10 @@ func (s *Server) channelRequest(data []byte) (server string, port uint16) {
 	return
 }
 
-func (s *Server) channelResponse() []byte {
+func (s *Server) channelResponse(errorCode int) []byte {
 	buf := new(bytes.Buffer)
 
-	binary.Write(buf, binary.LittleEndian, uint32(0))                                     // error code
+	binary.Write(buf, binary.LittleEndian, uint32(errorCode))                             // error code
 	binary.Write(buf, binary.LittleEndian, uint16(HTTP_CHANNEL_RESPONSE_FIELD_CHANNELID)) // fields present
 	binary.Write(buf, binary.LittleEndian, uint16(0))                                     // reserved
 
@@ -314,10 +331,10 @@ func (s *Server) channelResponse() []byte {
 	return createPacket(PKT_TYPE_CHANNEL_RESPONSE, buf.Bytes())
 }
 
-func (s *Server) channelCloseResponse() []byte {
+func (s *Server) channelCloseResponse(errorCode int) []byte {
 	buf := new(bytes.Buffer)
 
-	binary.Write(buf, binary.LittleEndian, uint32(0))                                     // error code
+	binary.Write(buf, binary.LittleEndian, uint32(errorCode))                             // error code
 	binary.Write(buf, binary.LittleEndian, uint16(HTTP_CHANNEL_RESPONSE_FIELD_CHANNELID)) // fields present
 	binary.Write(buf, binary.LittleEndian, uint16(0))                                     // reserved
 
diff --git a/cmd/rdpgw/protocol/types.go b/cmd/rdpgw/protocol/types.go
index a0d8e33b75162b66f67176e19bf3e8a88aef1837..eab67aa77fad88332b30ad47f03f9ebb78bff844 100644
--- a/cmd/rdpgw/protocol/types.go
+++ b/cmd/rdpgw/protocol/types.go
@@ -59,8 +59,8 @@ const (
 )
 
 const (
-	SERVER_STATE_INITIAL          = 0x0
-	SERVER_STATE_HANDSHAKE        = 0x1
+	SERVER_STATE_INITIALIZED = 0x0
+	SERVER_STATE_HANDSHAKE   = 0x1
 	SERVER_STATE_TUNNEL_CREATE    = 0x2
 	SERVER_STATE_TUNNEL_AUTHORIZE = 0x3
 	SERVER_STATE_CHANNEL_CREATE   = 0x4
diff --git a/dev/docker/Dockerfile b/dev/docker/Dockerfile
index 4c7dfe3659525f1bb202d9b801abba7a8860b4e0..0a8d2773dc6e116ca84e62aac9e1cbd44c4a4e36 100644
--- a/dev/docker/Dockerfile
+++ b/dev/docker/Dockerfile
@@ -25,6 +25,7 @@ RUN mkdir -p /opt/rdpgw && cd /opt/rdpgw && \
 RUN adduser --disabled-password --gecos "" --home /opt/rdpgw --uid 1001 rdpgw
 
 # build rdpgw and set rights
+ARG CACHEBUST
 RUN git clone https://github.com/bolkedebruin/rdpgw.git /app && \
     cd /app && \
     go mod tidy -compat=1.17 && \