Resolving 1006 WebSocket Connection Issues in Coder

Last updated: June 25, 2026

Applies to Coder v2 and use of:

VS Code Web 1.97+ and newer

Symptom

When using web-based applications in Coder, such as VS Code Web, the application fails to load correctly. Extensions (e.g., GitHub Copilot) do not function, and the browser's developer console shows a WebSocket close with status code 1006 error. This indicates an abnormal closure of the connection.

You may also observe that the WebSocket connection attempts are being made to an incorrect port (e.g., port 80 for a secure wss:// connection, or an internal-only port like 3000, 8000 or 8080).

Cause

This issue is almost always caused by a misconfiguration of the reverse proxy, ingress controller, or load balancer that sits in front of your Coder installation.

The typical scenario is:

  1. The proxy/load balancer terminates TLS (handling HTTPS traffic).
  2. It then forwards the request to Coder over plain HTTP.
  3. Coder, seeing a plain HTTP request, incorrectly constructs WebSocket URLs using ws:// or the wrong port, based on the headers it receives from the proxy (like X-Forwarded-Proto: http and X-Forwarded-Port: 80).

This problem is more prominent with recent versions of VS Code Web (v1.97 and newer), which are stricter about how they handle forwarded headers when establishing WebSocket connections. In some cases, this can also be triggered by HTTP/2 connection coalescing issues within the proxy.

Solution

The recommended approach is to correctly configure your proxy/ingress to forward proper headers to Coder so it can construct the correct WebSocket URLs. Alternatively, you can enable TLS directly on Coder.

Solution 1: Configure Proxy Headers (Recommended)

This is the most common deployment pattern where TLS is terminated at the proxy/ingress layer. Ensure Coder receives the correct X-Forwarded-Proto and related headers.

Step 1: Set Coder's Access URL

Ensure Coder is configured with the correct public-facing URL. Set at minimum these environment variables:

# Required: The external URL users access Coder at
CODER_ACCESS_URL=https://coder.example.com

# Optional: Required for port forwarding via subdomains
CODER_WILDCARD_ACCESS_URL=*.coder.example.com

For Helm deployments, configure these in values.yaml and recommend adding the CODER_PROXY_* or your X-Forwarded-For may be lost:

coder:
  env:
    - name: CODER_ACCESS_URL
      value: "https://coder.example.com"
    - name: CODER_WILDCARD_ACCESS_URL
      value: "*.coder.example.com"
    - CODER_PROXY_TRUSTED_HEADERS:
      value: "X-Forwarded-For,X-Forwarded-Proto"
    - CODER_PROXY_TRUSTED_ORIGINS:
      value: "10.0.0.0/8"  # LB pod CIDR

Step 2: Configure Your Proxy/Ingress

Ensure your proxy forwards the correct headers and properly handles WebSocket upgrades.

NGINX (Standalone)
server {
    server_name coder.example.com *.coder.example.com;

    listen 443 ssl;
    listen [::]:443 ssl;
    ssl_certificate /path/to/fullchain.pem;
    ssl_certificate_key /path/to/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;

        # WebSocket support
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection upgrade;

        # Forward correct headers
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
    }
}
NGINX Ingress Controller (Kubernetes)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: coder
  annotations:
    nginx.ingress.kubernetes.io/proxy-http-version: "1.1"
    nginx.ingress.kubernetes.io/configuration-snippet: |
      proxy_set_header X-Forwarded-Proto https;
      proxy_set_header X-Forwarded-Host $host;
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - coder.example.com
        - "*.coder.example.com"
      secretName: coder-tls
  rules:
    - host: coder.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: coder
                port:
                  number: 80
    - host: "*.coder.example.com"
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: coder
                port:
                  number: 80
Kubernetes Gateway API (HTTPRoute)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: coder
spec:
  parentRefs:
    - name: my-gateway
  hostnames:
    - coder.example.com
    - "*.coder.example.com"
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      filters:
        - type: RequestHeaderModifier
          requestHeaderModifier:
            set:
              - name: X-Forwarded-Proto
                value: https
      backendRefs:
        - name: coder
          port: 80
Caddy
coder.example.com, *.coder.example.com {
    reverse_proxy localhost:3000
}
Note: Caddy automatically handles WebSocket upgrades and sets correct forwarded headers.

Solution 2: Enable TLS Directly on Coder

If you prefer Coder to handle TLS termination directly, you can configure it with TLS certificates.

Environment Variables

CODER_TLS_ENABLE=true
CODER_TLS_CERT_FILE=/path/to/certificate.crt
CODER_TLS_KEY_FILE=/path/to/private.key
CODER_ACCESS_URL=https://coder.example.com
CODER_WILDCARD_ACCESS_URL=*.coder.example.com

Helm Chart Configuration

Create a Kubernetes TLS secret and reference it in values.yaml:

# Create the TLS secret
kubectl create secret tls coder-tls \
  --cert=/path/to/certificate.crt \
  --key=/path/to/private.key \
  -n coder
coder:
  env:
    - name: CODER_ACCESS_URL
      value: "https://coder.example.com"
    - name: CODER_WILDCARD_ACCESS_URL
      value: "*.coder.example.com"

  # Mount TLS certificates
  tls:
    secretNames:
      - coder-tls
Note: When using tls.secretNames, the Helm chart automatically sets CODER_TLS_ENABLE, CODER_TLS_CERT_FILE, and CODER_TLS_KEY_FILE. Do not set these manually.

With this configuration:

  • Coder listens on port 8080 (HTTP) and 8443 (HTTPS)
  • Your load balancer/ingress should use TLS passthrough or forward traffic to the HTTPS port

Using Helm Ingress with TLS Termination at Coder

If you want Coder to handle TLS and also use the built-in ingress support, configure the ingress to point to the HTTPS backend:

coder:
  env:
    - name: CODER_ACCESS_URL
      value: "https://coder.example.com"

  tls:
    secretNames:
      - coder-tls

  # When using TLS on Coder, typically you would NOT use
  # the built-in ingress as it creates HTTP backends.
  # Instead, use a LoadBalancer service or configure
  # ingress with TLS passthrough externally.
  ingress:
    enable: false

  service:
    type: LoadBalancer

Troubleshooting

Verify Headers Are Being Forwarded

Check the Coder server logs or use browser developer tools to verify the WebSocket URL being constructed. If you see URLs like ws:// instead of wss://, or incorrect ports, the headers are not being forwarded correctly.

Check for HTTP/2 Issues

Some proxies have issues with WebSocket upgrades over HTTP/2. Try forcing HTTP/1.1 between your proxy and Coder:

  • NGINX: proxy_http_version 1.1;
  • Envoy: Configure the cluster to use HTTP/1.1

Cloudflare Considerations

If using Cloudflare:

  • Ensure WebSockets are enabled in your Cloudflare dashboard
  • Verify SSL/TLS mode is set to "Full" or "Full (strict)"
  • Check that Cloudflare isn't modifying X-Forwarded-* headers unexpectedly

Common Misconfigurations

Warning: Do not manually set CODER_HTTP_ADDRESS, CODER_TLS_ENABLE, CODER_TLS_CERT_FILE, or CODER_TLS_KEY_FILE when using the Helm chart's tls.secretNames option, as these are automatically configured and will cause conflicts.

Related Resources