Today I learned something important about how Apache and Nginx handle WebSocket upgrades.
In Apache, it’s straightforward: you can use RewriteCond %{HTTP:Upgrade} =websocket
to detect when a client is actually requesting a WebSocket. Only then do you forward with the ws://
protocol and set the Connection/Upgrade
headers. If the request is just plain HTTP, you skip all that and proxy normally. Clean separation.
In Nginx, things work differently. If you follow common examples online, you’ll often see this in every location block:
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
The problem? That sends Connection: upgrade
even for normal HTTP requests. If your upstream isn’t expecting it, you can end up with weird parsing issues. It’s like telling your backend “this is a WebSocket” when it really isn’t.
The better approach in Nginx depends on your situation:
If you don’t use WebSockets at all:
Don’t set Upgrade
or Connection
headers anywhere. Just keep the proxy clean.
If you use WebSockets only on a certain path:
Put the Upgrade/Connection
headers in a dedicated location /ws/
block, and leave normal location /
untouched.
If you want to emulate Apache’s conditional logic everywhere:
Use a map
in nginx.conf
to set Connection
to "upgrade"
only when $http_upgrade
is present, otherwise "close"
.
Example:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
location / {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_pass http://127.0.0.1:8082;
}
Apache makes it easy to branch on WebSocket vs. HTTP with RewriteCond
. In Nginx, you either separate into different location
blocks or use a map
. And if you don’t need WebSockets at all, the cleanest solution is to drop the Upgrade/Connection
headers entirely.
Want me to also make a short one-paragraph version of this (like you did for the header-size blog)?