Learning Caddy - WebSocket & Real-time Applications
Episode 25 of 31

Learning Caddy - WebSocket & Real-time Applications

This episode covers WebSocket and real-time applications: automatic detection of upgraded connections, WebSocket reverse proxying, applications like Socket.io and real-time chat, timeout troubleshooting, and load balancer considerations for long-lived connections.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Modern applications need two-way, real-time communication: chat, notifications, collaborative editing, games. The technology behind it is WebSocket — a persistent connection where the server can push data without being asked. Episode 25 covers how Caddy handles WebSocket.

The good news: WebSocket in Caddy barely needs configuration. Caddy detects the upgrade header and proxies the connection automatically. But there are important nuances — timeouts, buffers, load balancers, and applications like Socket.io that need special attention.

After this episode, you'll be able to expose any real-time application to the internet with confidence.

WebSocket Support in Caddy

Automatic Detection

WebSocket starts as a regular HTTP request with an upgrade header:

Automatic WebSocket proxying
chat.example.com {
    reverse_proxy localhost:9000
}

Caddy sees the Upgrade: websocket and Connection: Upgrade headers, then lifts the connection into full-duplex. No special directive needed — reverse_proxy is enough.

Connection Upgrade and Keep-Alive

  • Upgrade: the handshake changes the protocol from HTTP to WebSocket.
  • Keep-alive: the connection is kept alive for the whole session, which can last hours.
  • Buffering: Caddy forwards frames bidirectionally without altering the content.

While the WebSocket proxy is active, Caddy maintains the connection to the backend as long as the client is connected. That's why timeouts for long-lived connections need attention.

Configuration and Considerations

Timeouts for Long-Lived Connections

WebSocket connections can last a very long time. If a timeout is dropping connections, check the transport options:

Set transport timeouts
chat.example.com {
    reverse_proxy localhost:9000 {
        transport http {
            read_timeout 1h
            write_timeout 1h
        }
    }
}

read_timeout 1h and write_timeout 1h give headroom for real-time connections. Caddy's defaults are already WebSocket-friendly, but if connections drop unexpectedly, make sure the timeouts aren't too short.

Headers for the Backend

Some applications need specific headers. For example, applications that identify the real client:

WebSocket headers
chat.example.com {
    reverse_proxy localhost:9000 {
        header_up X-Real-IP {remote_host}
        header_up X-Forwarded-For {remote_host}
    }
}

header_up X-Real-IP {remote_host} gives the application the client's real IP — important for rate limiting on the application side.

Common WebSocket Applications

Socket.io

Socket.io uses WebSocket with an additional protocol. For full integration:

Socket.io on Caddy
chat.example.com {
    reverse_proxy localhost:3000 {
        header_up X-Real-IP {remote_host}
    }
}

Socket.io also uses HTTP for the handshake and polling fallback. Because Caddy proxies both correctly, no special configuration is needed — reverse_proxy handles the HTTP handshake and the WebSocket upgrade at once.

Real-time Use Cases

  • Real-time chat: instant messages between users.
  • Live updates: stock prices, match scores, order statuses.
  • Collaborative editing: documents edited together (Google Docs style).
  • Game servers: game state synced to many clients.

All of these work through the same single reverse_proxy directive.

WebSocket Troubleshooting

Common Problems

  • Connection timeout: check read_timeout and write_timeout in the transport.
  • Proxy headers: make sure the application knows the real IP via header_up.
  • Load balancer: long-lived WebSocket connections don't mix well with round_robin for stateful applications — consider sticky sessions (episode 15).
  • Client compatibility: some legacy clients use a polling fallback; make sure it isn't blocked.

Logs and Debugging

When connections misbehave:

Debug WebSocket
caddy run --config Caddyfile --debug
curl -i -N -H "Connection: Upgrade" \
     -H "Upgrade: websocket" \
     -H "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==" \
     -H "Sec-WebSocket-Version: 13" \
     http://localhost/chat

curl -i -N -H "Connection: Upgrade" simulates a WebSocket handshake manually. If the response shows 101 Switching Protocols, proxying works. --debug shows the upgrade details in the logs.

Load Balancers and WebSocket

Long-Connection Considerations

A WebSocket connection isn't like a normal HTTP request that finishes in milliseconds. Considerations when using load balancing:

  • One connection sticks to one backend for a long time — distribution becomes less even.
  • Stateful applications with many WebSocket sessions should use sticky sessions.
  • Health checks keep running — a down backend gets no new connections, but existing ones will be dropped.

For stateless real-time applications, round_robin is still fine. For ones that store state in memory, add lb_policy cookie (episode 15).

Vertical vs Horizontal Scaling

WebSockets are synonymous with persistent connections that consume memory. Before adding many instances, make sure the application supports distributed state (Redis pub/sub, a shared store). Otherwise, more instances just split sessions apart.

Conclusion

Episode 25 covered WebSocket in Caddy: automatic upgrade detection, reverse_proxy handling full-duplex connections, applications like Socket.io and real-time chat, timeout and header troubleshooting, and load balancer considerations for long-lived connections.

Key takeaways:

  • WebSockets are proxied automatically by reverse_proxy — no special config.
  • Watch read_timeout and write_timeout for long-lived connections.
  • Socket.io works without additional configuration.
  • Sticky sessions help stateful applications with many WebSockets.
  • curl with upgrade headers can test the handshake.
  • Scaling requires shared state so sessions don't split apart.

In the next episode, episode 26, we'll cover gRPC & modern protocols — the HTTP/2 requirement for gRPC, reverse proxying with the h2c transport, gRPC load balancing, gRPC-Web for browsers, and enabling HTTP/3 (QUIC) with UDP port configuration.

Learning Caddy - WebSocket & Real-time Applications | Learning Caddy