Skip to content
Troubleshooting

Odoo WebSocket Not Connecting Through Nginx: The Full Fix

2026-07-18 · 11 min read

Last updated: 2026-08-09

WebSocket connection to 'wss://yourdomain.com/websocket' failed:
Error during WebSocket handshake: Unexpected response code: 400

Short answer: Nginx isn't passing through the HTTP Upgrade and Connection headers required for the WebSocket handshake, or the request never reaches a gevent-capable worker process in the first place. Both are Nginx/deployment configuration issues, not Odoo application bugs.

Which path applies to you

Odoo 16 and later moved from long-polling to real WebSockets, served at /websocket. Odoo 15 and earlier used pure long-polling at /longpolling. If you're not sure which applies, check your Odoo version, or check your browser's Network tab for which path is actually being requested. We cover the older long-polling case in more depth in our bus.Bus and Nginx post, this post focuses on the newer WebSocket path and the specific handshake failure.

The underlying reason for the change matters for troubleshooting, not just trivia. Long-polling works by holding an HTTP request open and repeatedly re-issuing it, which is a workaround for browsers not supporting true bidirectional connections over plain HTTP. WebSockets are a real protocol upgrade, a single persistent connection that both sides can push data over. That protocol-level difference is exactly why the fix is different too: long-polling failures are usually about request timeouts and buffering settings, while WebSocket failures are almost always about the HTTP-to-WebSocket upgrade handshake itself failing before a connection is ever established. If your symptom is "chat notifications are delayed but eventually arrive," that smells like a long-polling buffering issue. If your symptom is "the connection fails immediately with a 400 in the console," that's a WebSocket handshake failure, which is what this post is about.

Causes, ranked

1. Missing Upgrade/Connection headers in the Nginx location block

The single most common cause. A WebSocket handshake is an HTTP request with Upgrade: websocket and Connection: Upgrade headers. If your /websocket location block doesn't explicitly pass these through, the upstream (Odoo) never sees a valid upgrade request and returns a plain HTTP 400 instead of completing the handshake.

curl -I -H "Connection: Upgrade" -H "Upgrade: websocket" https://yourdomain.com/websocket
# a working config should NOT return a plain 400 here

2. No gevent-capable worker listening on the websocket port

Odoo's websocket handling runs through a dedicated long-polling/gevent worker process, separate from the regular HTTP workers, typically on port 8072. If you haven't configured a gevent worker (or the equivalent in your deployment), there's nothing correctly listening to upgrade the connection regardless of Nginx config.

sudo ss -tlnp | grep 8072

If nothing is listening on 8072, check your Odoo config for workers and confirm you're running odoo-bin with gevent support (via --workers greater than 0 and the odoo package's gevent dependency installed), not just relying on the default single-threaded dev server.

3. Nginx pointed at the wrong upstream port for websocket traffic

The websocket/longpolling upstream is a different port (commonly 8072) from the main web client (commonly 8069). Confirm your /websocket location block proxies to the correct upstream, not accidentally reusing the main web client's upstream block.

4. A load balancer or CDN in front of Nginx stripping upgrade headers

If you're behind Cloudflare or another proxy layer in front of your own Nginx, confirm that layer is also configured to pass WebSocket traffic through, some CDN configurations require WebSocket support to be explicitly enabled per-domain. Cloudflare specifically supports WebSockets on all plans, but the setting can be toggled off at the zone level, check Network settings in the dashboard if you're seeing failures that only happen when Cloudflare's proxy (orange cloud) is active and disappear when you grey-cloud the record temporarily to test.

5. Odoo running behind Docker with the wrong port published

If Odoo runs in a container, confirm both 8069 and 8072 are published, not just 8069. It's a common oversight to add the main web port to a docker-compose file and forget the second port used for websocket/longpolling traffic entirely, since the main site appears to work fine without it:

ports:
  - "8069:8069"
  - "8072:8072"

Confirm both are actually bound on the host after any compose change: docker compose ps should show both ports in the mapping, and sudo ss -tlnp | grep -E '8069|8072' should show both listening on the host.

The correct Nginx configuration

upstream odoo {
    server 127.0.0.1:8069;
}
upstream odoochat {
    server 127.0.0.1:8072;
}

server {
    listen 443 ssl;
    server_name yourdomain.com;

    location / {
        proxy_pass http://odoo;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # Odoo 16+ WebSocket path
    location /websocket {
        proxy_pass http://odoochat;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 3600s;
    }

    # Odoo 15 and earlier long-polling path
    location /longpolling {
        proxy_pass http://odoochat;
        proxy_set_header Host $host;
        proxy_read_timeout 720s;
        proxy_buffering off;
    }
}

proxy_http_version 1.1 is required, the WebSocket upgrade mechanism doesn't exist in HTTP/1.0, and Nginx's default proxy HTTP version is 1.0 unless explicitly set.

How to verify

Open your browser's DevTools, go to the Network tab, filter by "WS", and reload the page. A working connection shows status 101 Switching Protocols, not 400 or a failed connection. You can also confirm from the command line:

sudo tail -f /var/log/nginx/error.log
# trigger a page load that opens the websocket, watch for upgrade-related errors

Why the old and new paths coexist in some setups

Because Odoo 16 introduced /websocket as a replacement for the older /longpolling path, some multi-version fleets and some upgraded (rather than fresh-installed) Odoo instances still have both location blocks present in their Nginx config from a previous version's setup script. That's not automatically wrong, but it is worth confirming which path your actual running version uses so you're not maintaining a dead /longpolling block indefinitely, or worse, debugging the wrong one. Check the actual request path in your browser's Network tab rather than assuming from the Odoo major version number alone, since heavily customised instances occasionally pin an older bus implementation.

Still stuck

If headers are correctly configured and 8072 is listening but the handshake still fails, work through these before assuming it's unfixable:

  • Firewall or security group blocking 8072 specifically. Easy to miss when 8069 was opened but 8072 wasn't added at the same time: sudo ufw status | grep 8072.
  • SSL/TLS termination mismatch. If Nginx terminates SSL and proxies to Odoo over plain HTTP internally, confirm the wss:// (secure websocket) scheme is what the browser is requesting against your public domain, and that Nginx's own listen 443 ssl block is the one handling the /websocket location, not a separate plain-HTTP server block that doesn't have the upgrade headers configured.
  • A second Nginx or proxy layer in a container setup. Some Docker-based Odoo deployments ship their own internal Nginx or Traefik layer in addition to your host Nginx. If you have two proxy layers, both need the upgrade headers configured, not just the outer one, check for a proxy-inside-a-proxy topology before assuming your single Nginx config is the whole picture.
  • Browser-side ad blockers or corporate proxies. Rare, but worth a quick elimination check if the failure is inconsistent across users on the same network, since some corporate proxy appliances block or downgrade WebSocket traffic by policy regardless of server-side configuration.

Sources: Nginx documentation, Odoo documentation.

Talal Yousaf, Senior DevOps Engineer at DevFusion Tech. 70+ Odoo deployments across Community and Enterprise, versions 13 through 19. More about DevFusion · LinkedIn

Published 2026-07-18 · updated 2026-08-09

ShareLinkedInXWhatsApp

Hit the same wall in production?

Tell us what you're running and where it breaks. A 30 minute call, no pitch deck, and a straight answer on whether we can help.