I'm trying to proxy_pass a websocket with Nginx but I'm getting "502 Bad Gateway" and my Golang back end responds: "websocket: the client is not using the websocket protocol: 'upgrade' token not found in 'Connection' header".
Nginx config:
server {
listen 80;
server_name eg.example.com;
location / {
include proxy_params;
proxy_pass http://localhost:8000/;
}
location ~* /chatclientws/[\w\-]+ {
include proxy_params;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
# Added a dummy header to see if Nginx is passing the request properly.
proxy_set_header Dummy "Test";
proxy_pass "http://localhost:8000/chatclientws/$1/";
}
}
Proxy_params:
proxy_set_header Host $http_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 $scheme;
proxy_set_header X-NginX-Proxy true;
httputil.DumpRequest() in the Golang function for the route produces:
GET /chatclientws/13c21679-45b0-424a-872f-aa012a9ee7a0 HTTP/1.0
Host: eg.example.com
# Connection says close.
Connection: close
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,en-GB;q=0.8
Cache-Control: no-cache
# Connection says close again.
Connection: close
Cookie: clientroom=13c21679-45b0-424a-872f-aa012a9ee7a0
Origin: eg.example.com
Pragma: no-cache
# But websocket request does come through.
Sec-Websocket-Extensions: permessage-deflate; client_max_window_bits
Sec-Websocket-Key: ykQiDfJ2Tr2Z88WtnBQkAw==
Sec-Websocket-Version: 13
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36
X-Forwarded-For: 92.54.215.31
X-Forwarded-Proto: http
X-Nginx-Proxy: true
X-Real-Ip: 92.54.215.31
Somehow, Nginx is passing down all the information necessary except for the right Connection header.
Or so it seems. As seen above, I added a dummy header, proxy_set_header Dummy "Test";, to see if Nginx is really passing the headers down. The dummy header never makes it through.
On another page with the same Nginx config but without dynamically generated pathnames, dummy header and websocket connection work well. And, if I hardcode the pathname instead of using regex, like this:
location /chatclientws/1a904868-608d-42b2-9e02-4d7d4f8cef19 {
...
proxy_pass "http://localhost:8000/chatclientws/1a904868-608d-42b2-9e02-4d7d4f8cef19";
}
It works.
So, I believe I'm using regex wrongly here. But all the examples I've seen online seem to say my use is ok.
I'm puzzled as well as to how the websocket upgrade request comes through. Why is Nginx selectively passing down information?
How should I proceed from here?
After scouring the net and coming up short, I solved this by trial and error. So, this might not be the most professional solution. Anyway, it looks like we can't do regex with the location stanza in Nginx like this:
location ~* /chatclientws/[\w\-]+ {
...
proxy_pass "http://localhost:8000/chatclientws/$1";
}
}
Instead, do this:
# Name your regex variable.
location ~* /chatclientws/(?<Your_Variable_Name>[\w\-]+) {
...
# Then reference it in your proxy_pass.
proxy_pass "http://localhost:8000/chatclientws/$Your_Variable_Name";
}
Essentially, make your regex into named variables. I guess.
Related
I've had no luck finding an answer to this, please comment with what information you'd like me to share to help diagnose.
I have a laravel app which works perfectly fine in development, i develop on docker and the live servers use a docker swarm. The login page loads up fine, the database is connected fine and the user exists, but when i log in it just keeps redirecting back to the login screen.
The setup on the production server runs via an nginx reverse proxy to an nginx docker container with a php-fpm container linked. I'm wondering if this is somehow something to do with the reverse proxy which i don't use on the local development copy, so i'm pasting the reverse proxy config in case it helps:
server {
listen 443 ssl;
ssl_certificate /usr/local/etc/ssl/certs/live/example.com/cert.pem;
ssl_certificate_key /usr/local/etc/ssl/certs/live/example.com/privkey.pem;
ssl_session_timeout 10m;
ssl_verify_client off;
server_name admin.example.com;
error_log /usr/share/nginx/logs/error-admin.log;
access_log /usr/share/nginx/logs/access-admin.log;
client_max_body_size 1024M;
location / {
proxy_read_timeout 1800;
proxy_connect_timeout 60;
proxy_send_timeout 60;
proxy_next_upstream error timeout http_502;
proxy_next_upstream_tries 10;
proxy_pass http://admin;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_ssl_session_reuse on;
proxy_http_version 1.1;
proxy_set_header X-XSS-Protection 1;
proxy_set_header X-Content-Type-Options nosniff;
proxy_set_header Referrer-Policy origin;
proxy_set_header X-Frame-Options DENY;
proxy_set_header Host admin.example.com;
proxy_request_buffering off;
}
}
As i say let me know if you need any more details from other places, i'm not sure where to look for the answer to this.
The access log of the reverse proxy does show a 302 redirect after posting to login, not sure if this is any help:
8
6.147.208.76 - - [04/Jun/2022:15:20:03 +0000] "POST /login HTTP/1.1" 302 386 "https://admin.example.com/login" "Mozilla/5.0 (Macintosh;
Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko)
Version/15.3 Safari/605.1.15"
I am trying to run Laracvel-echo.
Host configuration:
location /socket.io {
proxy_pass http://127.0.0.1:6002/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
}
I catch headers with Socat:
socat -v TCP-LISTEN:6002,fork TCP:127.0.0.1:6001
On my DEV server, I see that everything is ok, the protocol is changing (HTTP/1.1 101 Switching Protocols):
2020/10/22 13:51:36.147102 length=175 from=0 to=174
HTTP/1.1 101 Switching Protocols\r
Upgrade: websocket\r
Connection: Upgrade\r
Sec-WebSocket-Accept: yU0AKhFOQaw5j9cQS8oq2bjx1pw=\r
Sec-WebSocket-Extensions: permessage-deflate\r
But in PRODUCTION server i see GET protocol (GET /socket.io HTTP/1.1):
2020/10/22 13:50:34.161919 length=588 from=0 to=587
GET /socket.io HTTP/1.1\r
Upgrade: websocket\r
Connection: Upgrade\r
Host: localhost:6002\r
Pragma: no-cache\r
Cache-Control: no-cache\r
Authorization: Basic YWlkYXI6MTIzMTIz\r
User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36\r
Origin: chrome-extension://pfdhoblngboilpfeibdedpjgfnlcodoo\r
Sec-WebSocket-Version: 13\r
Accept-Encoding: gzip, deflate\r
Accept-Language: ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7\r
Sec-WebSocket-Key: rq5CaFXEzZ/vWSnOJ5H3PA==\r
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\r
My question is related to this: WebSocket with Laravel-echo-server: 502 Bad gateway
Is the NGINX problem or am I wrong?
Help me please!)
I don't know what the problem is, but I found a solution.
I've updated a lot: updated Nginx to 1.19.1, possibly installed some additional modules. Installed npm not globally, but locally.
And it worked ...
Attention: this only works if you write location /socket.io.
I'm trying to deploy my first Clojure WebSocket app and I think I'm getting close. I get a good response locally, and it looks like the endpoint wants to face the outside world (I see that the port is open when I run netstat), but no response. I'm certain that I have something setup incorrectly in nginx.
I currently already host a few other websites on this server, just want to add the necessary config to get requests made to wss://domain.com:8001 to communicate with my app.
Here is the location entry I'm using now:
location / {
proxy_pass http://localhost:8001;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
access_log /var/www/logs/test.access.log;
error_log /var/www/logs/test.error.log;
}
Could anyone help point me in the right direction? My guess is that I actually have too much in the config, and what's there is probably not correct.
** EDIT: ** For interested parties, I put up my working config (based on Erik Dannenberg's answer) in a gist.
You are missing two more headers, a minimal working config:
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
# add the two below
proxy_set_header Upgrade websocket;
proxy_set_header Connection upgrade;
# optional, but helpful if you run into timeouts
proxy_read_timeout 86400;
}
I'm running a Joomla site running on a Plesk VPS with Apache + nginx. In the "Web Server Settings" in Plesk for that domain under "Additional nginx directives" (which will override the server-wide nginx configuration) I have specified:
add_header Cache-Control "private, max-age=604800, must-revalidate";
All works well and the site now gets served with proper cache headers added and Google PageSpeed stopped complaining - but I'm getting some errors with back-end functions now such as when uploading batches of images to my website's gallery. This seems to be related to the above as it works normally again when the directive is removed.
How can I re-write the additional nginx directive above to exclude the /administrator/ directory of my site from having any cache headers added by nginx?
Use a location block that negates the path you don't want the header added to:
location ^~ /administrator/ {
add_header Cache-Control "private, max-age=604800, must-revalidate";
}
If you have nginx+apache:
add_header Cache-Control "private, max-age=604800, must-revalidate";
location ^~ /administrator/ {
add_header Cache-Control "no-cache, max-age=1";
proxy_pass http://[....IP address of domain ....]:7080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
I've got an Nginx/Gunicorn/Django server deployed on a Centos 6 machine with only the SSL port (443) visible to the outside world. So unless the server is called with the https://, you won't get any response. If you call it with an http://domain:443, you'll merely get a 400 Bad Request message. Port 443 is the only way to hit the server.
I'm using Nginx to serve my static files (CSS, etc.) and all other requests are handled by Gunicorn, which is running Django at http://localhost:8000. So, navigating to https://domain.com works just fine, as do links within the admin site, but when I submit a form in the Django admin, the https is lost on the redirect and I'm sent to http://domain.com/request_uri which fails to reach the server. The POST action does work properly even so and the database is updated.
My configuration file is listed below. The location location / section is where I feel like the solution should be found. But it doesn't seem like the proxy_set_header X-* directives have any effect. Am I missing a module or something? I'm running nginx/1.0.15.
Everything I can find on the internet points to the X-Forwarded-Protocol https like it should do something, but I get no change. I'm also unable to get the debugging working on the remote server, though my next step may have to be compiling locally with debugging enabled to get some more clues. The last resort is to expose port 80 and redirect everything...but that requires some paperwork.
[http://pastebin.com/Rcg3p6vQ](My nginx configure arguments)
server {
listen 443 ssl;
ssl on;
ssl_certificate /path/to/cert.crt;
ssl_certificate_key /path/to/key.key;
ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
server_name example.com;
root /home/gunicorn/project/app;
access_log /home/gunicorn/logs/access.log;
error_log /home/gunicorn/logs/error.log debug;
location /static/ {
autoindex on;
root /home/gunicorn;
}
location / {
proxy_pass http://localhost:8000/;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Protocol https;
}
}
Haven't had time yet to understand exactly what these two lines do, but removing them solved my problems:
proxy_redirect off;
proxy_set_header Host $host;