I've been able to find guides pertaining to various combinations of nginx, node, ssl, and websockets, but never all together and never on a single server.
Ultimately, this is what I'm after and I'm not sure if it's even possible:
single server (Ubuntu 14.04)
forced HTTPS (browsing to http://site forwards to https://)
node app is hosted on localhost:3000
node app uses web sockets
it's a single-page React app with no routing at all, so I don't need routes. I repeat, I'm only hosting one page with no navigation whatsoever.
With the below config, I have everything working except websockets - the client throws an error which doesn't happen if I browse straight to the node server and don't use nginx (browse to http://my.domain:3000):
bundle.js:26 WebSocket connection to 'wss://<my domain>/socket.io/?EIO=3&transport=websocket&sid=x1uQtRzF3gYYEvfIAAAi' failed: Error during WebSocket handshake: Unexpected response code: 400
server {
listen 80;
return 301 https://my/domain$request_uri;
}
server {
listen 443 ssl;
listen [::]:443;
ssl_certificate /path/cert.crt;
ssl_certificate_key /path/key.key;
ssl_session_cache shared:SSL:10m;
server_name blaze.chat;
location / {
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_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Host $host;
proxy_redirect http://localhost:3000 https://my.domain;
}
}
Right, got it working... Found a lot of articles all showing similar things but missing these key lines:
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400;
In my case, websockets won't work without those lines, contrary to many other posts where similar questions were asked. Not sure what the difference was. Ironically it is listed as a requirement in the Nginx Websocket Proxying doco... Should have started there, my bad.
http://nginx.org/en/docs/http/websocket.html
Side note, I am just using this on the root path of / which works fine.
Related
We have a nodejs app that currently uses socket.io ( with namespaces ). This app is used as a dashboard for a specific financial market. Each instance of app subscribe to a specific market data and provides a dashboard. Initially we were running 3 separate instances of this app configured for 3 separate markets on the server, all binding to separate ports for serving requests.
Since we plan to add more markets it makes sense to have a reverse proxy server where a single port (along with separate URI for each market) can be used. However, setting up nginx has been a nightmare for various reasons.
(a) each instance of app for a market can be in different development stage and hence can have different static files. Managing all static file via nginx seems painful ? What can be done to leave handling of the static files with the app itself.
(b) socket.io communication is a failure. We tried to look into network communication and it seems it keeps on getting 404 page not found error when trying to connect to socket.io server. Not sure why it is connecting via http::/localhost/server.io/ instead of ws://localhost/server.io/ ? Can somebody point us to a similar example ? Anything that needs to be taken care of ?
IN our case we have been trying the following inside nginx sites-available/default
location /app/ {
proxy_pass http://localhost:3000/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
#proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# kill cache
add_header Last-Modified $date_gmt;
add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
if_modified_since off;
expires off;
etag off;
}
Using nginx as a reversed proxy should not give you a hard time. The great thing about nginx is that you can have multiple projects on the same server with different domains.
Here is an example of nginx with multiple projects:
server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://localhost:3000;
#Rember to set the header like this otherwise the socket might not work.
proxy_set_header X-Real-Ip $remote_addr;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
}
server {
listen 80;
server_name subdomain.yourdomain.com;
location / {
proxy_pass http://localhost:3001;
}
}
I'm not sure why your socket should fail. Perhaps the mistake is that you try to define the route on the client site. Try having the javascript like this:
var socket = io();
or if your socket runs on one of your other applications:
var socket = io('http://yourdomain.com');
And remember that your changes should be added to sites-enabled instead of sites-avaible
Hard to write a proper title.
I have a pure websocket application which is based on an express server. In development mode the express server does some simple routing to access the html, js and png files. Basically it is a one page app that only handle websocket protocol. But in production mode, I delegate all that to Nginx so I don't do any routing in express production. I was expecting Nginx will find all these files on its "root" directory. I get a "Cannot Get /" error however.
TL;DR version:
When I use Nginx to serve static contents for a one page app, do I need to do anything in express routing? If so, how?
UPD (my Nginx settings, can find another more detail question here):
upstream upstream_project {
server 127.0.0.1:8888;
keepalive 64;
}
server {
listen 0.0.0.0:80 default_server;
listen [::]:80 default_server ipv6only=on;
root /usr/local/share/html;
index index.html index.htm;
# Make site accessible from http://localhost/
server_name ws_server;
location / {
try_files $uri $uri/ index.html;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://upstream_project;
proxy_read_timeout 240s;
}
}
UPDATED:
I have found in Nginx log that the client is trying to access the server via ipv6. See this:
kevent() reported that connect() failed (61: Connection refused) while
connecting to upstream, client: ::1, server: ws_server, request: "GET /
HTTP/1.1", upstream: "http://127.0.0.1:8888/", host: "localhost"
I remove the ipv6 server listen line (listen [::]:80 default_server ipv6only=on;) and have to change the try_files line to use file name explicitly.
try_files $uri $uri/app.js ;
Then I can get my app working. But I don't understand. Why do I have to do this?
I still can't access the static png file from the subdirectory of "root" folder. Any help would be appreciated.
NGINX supports WebSocket by allowing a tunnel to be set up between a client and a backend server. For NGINX to send the Upgrade request from the client to the backend server, the Upgrade and Connection headers must be set explicitly, as in this example:
location /wsapp/ {
proxy_pass http://wsbackend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
Take a look on this NGINX guide for more details and examples.
I'm trying a new server configuration using an nginx reverse proxy and ssl, but it seems to break my google OAuth2. I'm using node v6.2.2, pm2 to manage nodejs, and using nginx for ssl and a reverse proxy.
My Nginx server blocks look like:
server {
listen 80;
server_name _;
return 301 https://$host$request_uri;
}
and
server {
listen 443;
ssl on;
include snippets/ssl-example.com.conf;
include snippets/ssl-params.conf;
server_name example.com;
location / {
proxy_pass http://localhost:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
When running the nodejs server on my laptop I'm able to login using passportjs' google strategy with no issues, but as soon as I run the same code behind the reverse proxy I get a redirect_uri_mismatch. I've tried hardcoding the callbackURL to http://example.com/auth/oauthCallback and https://example.com/auth/oauthCallback and have added all variations of those to the OAuth Client IDs. I've tried making small changes to my server blocks and couldn't make much headway, so here I am.
Any ideas for a next step?
Turns out Google's OAuth2 doesn't seem to recognize .xyz domains
I managed to deploy meteor on my infrastructure (Webfactions).
The application seems to work fine but I get the following error in the browser console when my application starts:
WebSocket connection to 'ws://.../websocket' failed: Error during WebSocket handshake: Unexpected response code: 400
WebSockets are fast and you don't have to (and shouldn't) disable them.
The real cause of this error is that Webfactions uses nginx, and nginx was improperly configured. Here's how to correctly configure nginx to proxy WebSocket requests, by setting proxy_set_header Upgrade $http_upgrade; and proxy_set_header Connection $connection_upgrade;:
# we're in the http context here
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
# the Meteor / Node.js app server
server {
server_name yourdomain.com;
access_log /etc/nginx/logs/yourapp.access;
error_log /etc/nginx/logs/yourapp.error error;
location / {
proxy_pass http://localhost:3000;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host; # pass the host header - http://wiki.nginx.org/HttpProxyModule#proxy_pass
proxy_http_version 1.1; # recommended with keepalive connections - http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_http_version
# WebSocket proxying - from http://nginx.org/en/docs/http/websocket.html
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
This is an improved nginx configuration based on David Weldon's nginx config. Andrew Mao has reached a very similar configuration.
Remember to also set the HTTP_FORWARDED_COUNT environment variable to the number of proxies in front of the app (usually 1).
if you are receiving this error client side in the browser console, you can safely ignore it - it means that your hosting does not support websockets and meteor will fallback to using long polling instead
meteor apps deployed to heroku or any other platform without websockets will get the same error
update:
as of meteor v0.6.4 you can now set the environment variable DISABLE_WEBSOCKETS to prevent this attempt from occurring if you know it will fail
https://github.com/meteor/meteor/blob/devel/History.md
If you set the DISABLE_WEBSOCKETS environment variable, browsers will not attempt to connect to your app using Websockets. Use this if you know your server environment does not properly proxy Websockets to reduce connection startup time.
Concerning SEO: the failing websocket (code 400) also prevents Phantomjs for getting a decent pageload (and doesn't get terminated).
In my case, the new Nginx configuration from Dan prevents the failing of the websockets and lets Phantomjs load the page.
Found this in my search for this error when using AWS Elastic Load Balancer. Setting the environment variable works, but the better solution is to use the TCP protocol on the ELB instead of HTTPS. FYI.
We had the issue with disabled websocket when hosting Rocket Chat. Our users get "Websocket is disabled for this server" error when they tryed to connect to the server from mobile clients for iOS and Android.
Thanks to Dan's post, adding this lines to config helped us:
#websocket support
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
Thus way our config became like this:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 443 ssl http2;
server_name our_rocketchat_domain_url.com;
ssl_certificate "/etc/letsencrypt/live/our_rocketchat_domain_url.com/fullchain.pem";
ssl_certificate_key "/etc/letsencrypt/live/our_rocketchat_domain_url.com/privkey.pem";
ssl_ciphers our_ciphers_list;
ssl_prefer_server_ciphers on;
ssl_protocols TLSv1.2 TLSv1.3;
access_log /var/log/nginx/our_rocketchat_domain_url.com.access.log;
error_log /var/log/nginx/our_rocketchat_domain_url.com.error.log warn;
location / {
proxy_buffers 16 4k;
proxy_buffer_size 2k;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_http_version 1.1;
proxy_pass http://ip_of_our_rocketchat:3000/;
#websocket support
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
I am trying to setup nginx as a reverse rpoxy server in front off several IIS web servers who are authenticating using Basic authentication.
(note - this is not the same as nginx providing the auth using a password file - it should just be marshelling everythnig between the browser/server)
Its working kind off - but getting repeatedly prompted for auth by every single resource (image/css etc) on a page.
upstream my_iis_server {
server 192.168.1.10;
}
server {
listen 1.1.1.1:80;
server_name www.example.com;
## send request back to my iis server ##
location / {
proxy_pass http://my_iis_server;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass_header Authorization;
proxy_redirect off;
proxy_buffering off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
This exact situation took me forever to figure out, but OSS is like that I guess. This post is a year old so maybe the original poster figured it out, or gave up?
Anyway, the problem for me at least was caused by a few things:
IIS expects the realm string to be the same as what it sent to Nginx, but if your Nginx server_name is listening on a different address than the upstream then the server side WWW-Authenticate is not going to be what IIS was expecting and ignore it.
The builtin header module doesn't clear the other WWW-Authenticate headers, particularly the problematic WWW-Authenticate: Negotiate. Using the headers-more module clears the old headers, and adds whatever you tell it to.
After this, I was able to finally push Sharepoint 2010 through Nginx.
Thanks stackoverflow.
server {
listen 80;
server_name your.site.com;
location / {
proxy_http_version 1.1;
proxy_pass_request_headers on;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#proxy_pass_header Authorization; //This didnt work for me
more_set_input_headers 'Authorization: $http_authorization';
proxy_set_header Accept-Encoding "";
proxy_pass https://sharepoint/;
proxy_redirect default;
#This is what worked for me, but you need the headers-more mod
more_set_headers -s 401 'WWW-Authenticate: Basic realm="intranet.example.com"';
}
}
I had these same symptoms with nginx/1.10.3. I have a service secured under basic authentication, and nginx as a reverse proxy between the clients and the server. The requirement was that nginx would passthrough the authorization.
First request to the server did pass through the Authorization header. Second request simply blocked this header, which meant the client was only able to make one request per session.
This was somehow related to cookies. If I cleared the browser cookies, then the cycle repeated. The client was able to authenticate but just for the first request. Closing the browser had the same effect.
The solution for me was to change the upstream server from https to http, using:
proxy_pass http://$upstream;
instead of:
proxy_pass https://$upstream;