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.

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 starts as a regular HTTP request with an upgrade header:
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.
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.
WebSocket connections can last a very long time. If a timeout is dropping connections, check the transport options:
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.
Some applications need specific headers. For example, applications that identify the real client:
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.
Socket.io uses WebSocket with an additional protocol. For full integration:
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.
All of these work through the same single reverse_proxy directive.
read_timeout and write_timeout in the transport.header_up.round_robin for stateful applications — consider sticky sessions (episode 15).When connections misbehave:
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/chatcurl -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.
A WebSocket connection isn't like a normal HTTP request that finishes in milliseconds. Considerations when using load balancing:
For stateless real-time applications, round_robin is still fine. For ones that store state in memory, add lb_policy cookie (episode 15).
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.
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:
reverse_proxy — no special config.read_timeout and write_timeout for long-lived connections.curl with upgrade headers can test the handshake.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.