Skip to content

Instantly share code, notes, and snippets.

@gmanau
Last active March 25, 2024 12:15
Show Gist options
  • Star 53 You must be signed in to star a gist
  • Fork 21 You must be signed in to fork a gist
  • Save gmanau/4f5bdb223d1e13417a3e to your computer and use it in GitHub Desktop.
Save gmanau/4f5bdb223d1e13417a3e to your computer and use it in GitHub Desktop.
How to setup nginx as nodejs/socket.io reverse proxy over SSL
upstream upstream-apache2 {
server 127.0.0.1:8080;
}
upstream upstream-nodejs {
server 127.0.0.1:3000;
}
server {
listen 80;
server_name mydomain.com www.mydomain.com;
rewrite ^(.*) https://$host$1 permanent;
}
server {
listen 443 ssl;
ssl on;
server_name mydomain.com www.mydomain.com;
access_log /var/log/nginx/access-ssl.log;
error_log /var/log/nginx/error-ssl.log;
ssl_certificate /etc/nginx/ssl/wasmycertificate.crt;
ssl_certificate_key /etc/nginx/ssl/mycertificate.key;
ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers RC4:HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
keepalive_timeout 60;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
large_client_header_buffers 8 32k;
location / {
proxy_pass http://upstream-apache2;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
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;
proxy_set_header X-Forwarded-Proto $scheme;
add_header Front-End-Https on;
}
location /socket.io/ {
proxy_pass http://upstream-nodejs;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
@CWFranklin
Copy link

Made a few tweaks to get it working with Certbot and not sure what's so different about your config to the one I had before other than that I wasn't separating /socket.io/ traffic but this led me to a working config after days of trying. Just wanted to say thanks <3
For some reason what was working locally wasn't working on my server.

@kylezinter
Copy link

@CWFranklin Can you share what you ended getting to that worked? I'm having an issue that sounds very similar and also using Certbot.

@zsimo
Copy link

zsimo commented May 15, 2019

location /socket.io/ works for me, thanks!

@outbreak
Copy link

outbreak commented Jun 18, 2019

const nsp = io.of('/my-namespace')

nsp.use((socket, next) => {
  socket.handshake.address = socket.handshake.headers['x-real-ip']
    && socket.handshake.headers['x-real-ip']

  next()
})

nsp.on('connect', (socket) => {
  console.log('real ip', socket.handshake.address) // your real ip address
})

@eadsjr
Copy link

eadsjr commented Jun 21, 2019

It might be worth mentioning that SSLv3 and TLSv1 are considered insecure now, and should probably be dropped from examples people are going to use.

@nikitalpopov
Copy link

@CWFranklin I have the same problem. Could you share your config, please?

@CWFranklin
Copy link

CWFranklin commented Nov 28, 2019

@nikitalpopov @kylezinter (apologies I never got a notification for your comment)
Config as below (domains changed).

upstream my-domain {
        server 127.0.0.1:4001;
}

server {
        listen          80;
        server_name     sub.example.com;
        rewrite         ^(.*)   https://$host$1 permanent;
}

server {
        listen 443 ssl; # managed by Certbot

        server_name sub.example.com;

        ssl_certificate /etc/letsencrypt/live/sub.example.com/fullchain.pem; # managed by Certbot
        ssl_certificate_key /etc/letsencrypt/live/sub.example.com/privkey.pem; # managed by Certbot
        include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
        ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

        location / {
                proxy_pass              http://my-domain;
                proxy_next_upstream     error timeout invalid_header http_500 http_502 http_503 http_504;
                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;
                proxy_set_header        X-Forwarded-Proto       $scheme;
                add_header              Front-End-Https         on;
        }

        location /socket.io/ {
                proxy_pass              http://my-domain;
                proxy_redirect          off;

                proxy_http_version      1.1;

                proxy_set_header        Upgrade                 $http_upgrade;
                proxy_set_header        Connection              "upgrade";
                proxy_set_header        Host                    $host;
                proxy_set_header        X-Real-IP               $remote_addr;
                proxy_set_header        X-Forwarded-For         $proxy_add_x_forwarded_for;
        }
}

@nikitalpopov
Copy link

@CWFranklin thanks a lot! 😄

@rafaelcalhau
Copy link

Worked like a charm. Thanks a lot :D

@eburhan
Copy link

eburhan commented Feb 14, 2020

Thanks. This configuration solved my problem :)

@luongyen123
Copy link

@eburhan my config not working
upstream my-domain {
server 127.0.0.1:4001;
}
server {
listen 80;

server_name www.mydomain;
rewrite ^(.*) http://mydomain$1 permanent;

}

server {
listen 80 default_server;

# access_log off;
access_log /home/mydomain/logs/access.log;
# error_log off;
	error_log /home/mydomain/logs/error.log;

	root /home/mydomain/public_html/Canvasee_apigateway/public;
index index.php index.html index.htm;
	server_name mydomain;

	location / {
	
	try_files $uri $uri/ /index.php?$args;
	 proxy_pass              http://my-domain;
            proxy_next_upstream     error timeout invalid_header http_500 http_502 http_503 http_504;
            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;
            proxy_set_header        X-Forwarded-Proto       $scheme;
            add_header              Front-End-Https         on;
}

# Custom configuration
include /home/mydomain/public_html/*.conf;

	location ~ \.php$ {
	fastcgi_split_path_info ^(.+\.php)(/.+)$;
    	include /etc/nginx/fastcgi_params;
    	fastcgi_pass 127.0.0.1:9000;
    	fastcgi_index index.php;
	fastcgi_connect_timeout 1000;
	fastcgi_send_timeout 1000;
	fastcgi_read_timeout 1000;
	fastcgi_buffer_size 256k;
	fastcgi_buffers 4 256k;
	fastcgi_busy_buffers_size 256k;
	fastcgi_temp_file_write_size 256k;
	fastcgi_intercept_errors on;
    	fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
	}

location /nginx_status {
	stub_status on;
	access_log   off;
	allow 127.0.0.1;
	allow 15.164.118.81;
	deny all;
}

location /php_status {
	fastcgi_pass 127.0.0.1:9000;
	fastcgi_index index.php;
	fastcgi_param SCRIPT_FILENAME  $document_root$fastcgi_script_name;
	include /etc/nginx/fastcgi_params;
	allow 127.0.0.1;
	allow 15.164.118.81;
	deny all;
	}

# Disable .htaccess and other hidden files
location ~ /\.(?!well-known).* {
	deny all;
	access_log off;
	log_not_found off;
}

    location = /favicon.ico {
            log_not_found off;
            access_log off;
    }

location = /robots.txt {
	allow all;
	log_not_found off;
	access_log off;
}

location ~* \.(3gp|gif|jpg|jpeg|png|ico|wmv|avi|asf|asx|mpg|mpeg|mp4|pls|mp3|mid|wav|swf|flv|exe|zip|tar|rar|gz|tgz|bz2|uha|7z|doc|docx|xls|xlsx|pdf|iso|eot|svg|ttf|woff)$ {
        gzip_static off;
	add_header Pragma public;
	add_header Cache-Control "public, must-revalidate, proxy-revalidate";
	access_log off;
	expires 30d;
	break;
    }

    location ~* \.(txt|js|css)$ {
        add_header Pragma public;
	add_header Cache-Control "public, must-revalidate, proxy-revalidate";
	access_log off;
	expires 30d;
	break;
    }

listen 443 ssl; # managed by Certbot

ssl_certificate /etc/letsencrypt/live/mydomain/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/mydomain/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}
server {
listen 2020;

access_log off;
log_not_found off;
error_log /home/mydomain/logs/nginx_error.log;

	root /home/mydomain/private_html;
index index.php index.html index.htm;
	server_name mydomain;

auth_basic "Restricted";
auth_basic_user_file /home/mydomain/private_html/hocvps/.htpasswd;

 	location / {
	autoindex on;
	try_files $uri $uri/ /index.php;
}

	location ~ \.php$ {
	fastcgi_split_path_info ^(.+\.php)(/.+)$;
    	include /etc/nginx/fastcgi_params;
    	fastcgi_pass 127.0.0.1:9000;
    	fastcgi_index index.php;
	fastcgi_connect_timeout 1000;
	fastcgi_send_timeout 1000;
	fastcgi_read_timeout 1000;
	fastcgi_buffer_size 256k;
	fastcgi_buffers 4 256k;
	fastcgi_busy_buffers_size 256k;
	fastcgi_temp_file_write_size 256k;
	fastcgi_intercept_errors on;
    	fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
	}

location ~ /\. {
	deny all;
}

}

@tamaker
Copy link

tamaker commented May 15, 2020

@luongyen123 curious to know if you ever got your's working? What did you find was the issue? I'm battling the same thing now. Thanks!

@devinshawntripp
Copy link

hey @CWFranklin do you have the way you setup your node server by chance? I can't seem to get mine working for days now.

Here is my server.js file

const express = require('express')
require('dotenv').config();
const https = require('https')
const path = require('path');
var socketio = require('socket.io')
const bodyParser = require('body-parser')
var PORT = process.env.PORT || 8174;
const app = express()
const cors = require('cors');
const db = require('./db');
const fs = require('fs');
var allowedOrigins = "http://localhost:3000"
const options = {
    cors: {
            origin: "*",
        methods: ["GET", "POST"],
        transports: ['websocket'],
        credentials: true
    },
    allowEI03: true,
    credentials: true,
    origins: ['http://127.0.0.1:8174']
}
const CORS_fn = (req, res) => {
    res.setHeader( "Access-Control-Allow-Origin"     , "*"    );
    res.setHeader( "Access-Control-Allow-Credentials", "true" );
    res.setHeader( "Access-Control-Allow-Methods"    , "*"    );
    res.setHeader( "Access-Control-Allow-Headers"    , "*"    );
    if ( req.method === "OPTIONS" ) {
        res.writeHead(200);
        res.end();
        return;
    }
};


db.on('error', console.error.bind(console, 'MongoDB connection Error'))



const keyOptions = {
        cors: {
                origin: '*',
                methods: ["GET", "POST"],
                transports: ['websocket'],
                credentials: true
        },
        key: fs.readFileSync('/etc/letsencrypt/live/idealgambler.com-0002/privkey.pem'),
        cert: fs.readFileSync('/etc/letsencrypt/live/idealgambler.com-0002/cert.pem'),
        ca: fs.readFileSync('/etc/letsencrypt/live/idealgambler.com-0002/chain.pem'),
        requestCert: false,
        rejectUnauthorized: false,
        allowEI03: true,
        credentials: true,
        origins: ['https://127.0.0.1:8174']
}



const server = https.createServer(keyOptions, app);
const io = socketio(server)

/*

app.use('*', cors());
app.use(express.json());

const io = socketio(server, options);


const casino = require('./Casino-Routers/Craps.js')(app , io)

app.use('/', casino);
*/
if(process.env.NODE_ENV === "production"){
    app.use(express.static(path.join(__dirname, "../../frontend/CrapsReact/build")));
    app.get("*", (request, res) => {
        res.sendFile(path.join(__dirname, "../../frontend/CrapsReact/build", "index.html"))
    })
} else {
    PORT = 8174
}

server.listen(PORT, () => console.log(`Server running on port ${PORT}`));



io.sockets.on('connection', function(socket) {
        console.log("Socket connected with id: ", socket.id);

});

as you can see I tried to make it into an https server and this did not work. any help is appreciated

@CWFranklin
Copy link

@devinshawntripp If you're using NGINX then I'd let NGINX handle your SSL. I wouldn't bother with the keyOptions you've set at all.
Other thing to make sure of is that you've configured the right ports (8174 or whatever is in your PORT env) in NGINX as well. If you share your NGINX config that'd potentially give some clues as well

@devinshawntripp
Copy link

devinshawntripp commented Oct 5, 2021

@CWFranklin

So here is my actual working backend code

const express = require('express')
require('dotenv').config();
const http = require('http')
const path = require('path');
var socketio = require('socket.io')
const bodyParser = require('body-parser')
const PORT = process.env.PORT || 8174;
const app = express()
const cors = require('cors');
const db = require('./db');



var allowedOrigins = "http://localhost:3000"


const options = {
    cors: {
        origin: "*",
        methods: ["GET", "POST"],
        transports: ['websocket', 'polling'],
        credentials: true
    }, 
    allowEI03: true,
    origins: ['http://127.0.0.1:8174']
}


db.on('error', console.error.bind(console, 'MongoDB connection Error'))

const server = http.createServer(app);


app.use('*', cors());
app.use(express.json());

const io = socketio(server, options);



const casino = require('./Casino-Routers/Craps.js')(app , io)

app.use('/', casino);

if(process.env.NODE_ENV === "production"){
    app.use(express.static(path.join(__dirname, "../frontend/build")));
    app.get("*", (request, res) => {
        res.sendFile(path.join(__dirname, "../frontend/build", "index.html"))
    })
} else {
    PORT = 8174
}


server.listen(PORT, () => console.log(`Server running on port ${PORT}`))

And the nginx is the same as the one you posted above. I’ll post when I get home in a few hours.

The errors I’m getting are:

GET https://googleCloudStaticip/InitialGame net::ERR_CERT_COMMON_NAME_INVALID

websocket.js:88 WebSocket connection to 'wss://googleCloudStaticip:8174/socket.io/?EIO=4&transport=websocket' before socket could connect
websocket.js:88 WebSocket connection to 'wss://googleCloudStaticip:443/socket.io/?EIO=4&transport=websocket' failed: 
websocket.js:88 WebSocket connection to 'wss://googleCloudStaticip/socket.io/?EIO=4&transport=websocket' failed: 
websocket.js:88 WebSocket connection to 'wss://googleCloudStaticip:80/socket.io/?EIO=4&transport=websocket' failed: 
websocket.js:88 WebSocket connection to 'wss://googleCloudStaticip:8174/socket.io/?EIO=4&transport=websocket' failed: 

Error connection to socket closed before it could connect as well as many others

@devinshawntripp
Copy link

devinshawntripp commented Oct 6, 2021

@CWFranklin

Here is my nginx file its handles the SSL. I am using certbot to generate certs for both www and blank.domains .

upstream socketio {
#       ip_hash
        server 127.0.0.1:8174;
}


server {
        listen 80;
        server_name mydomain.com www.mydomain.com;
        rewrite ^(.*) https://$host$1 permanent;
}




server {

        listen                  443 ssl;

        ssl                     on;
        server_name             mydomain.com www.mydomain.com;

        access_log              /var/log/nginx/access-ssl.log;
        error_log               /var/log/nginx/error-ssl.log;

        ssl_certificate /etc/letsencrypt/live/mydomain.com-0002/fullchain.pem; # managed by Certbot
        ssl_certificate_key /etc/letsencrypt/live/mydomain.com-0002/privkey.pem; # managed by Certbot
        include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
        ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; 
large_client_header_buffers 8 32k;

        location / {
                include proxy_params;
                proxy_pass              http://socketio;
                proxy_next_upstream     error timeout invalid_header http_500 http_502 http_503 http_504;
                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;
                proxy_set_header        X-Forwarded-Proto       $scheme;
                add_header              Front-End-Https         on;
        }

        location /socket.io/ {
                proxy_pass              http://socketio;
                proxy_redirect off;

                proxy_http_version      1.1;

                proxy_set_header        Upgrade                 $http_upgrade;
                proxy_set_header        Connection              "upgrade";

                proxy_set_header        Host                    $host;
                proxy_set_header        X-Real-IP               $remote_addr;
                proxy_set_header        X-Forwarded-For         $proxy_add_x_forwarded_for;
        }

}

@devinshawntripp
Copy link

devinshawntripp commented Oct 6, 2021

I think I maybe need to down grade my socket io or I need to reimplement how I have my sockets and backend working. I have them in the same file. like the enpoint InitializeGame is in the same file as io.on(connection);

oh and I am using this on the client

export const Socket = socketio.connect('//ipaddr' , { transports: ['websocket'], secure: true, reconnect: true, rejectUnauthorized: false})

@devinshawntripp
Copy link

Nevermind I figured out the issue on my own to anyone else having a ERR_CERT_UNCOMMON NAME issue you might want to check that you are accessing your domain name in the socketio.connect(actualdomainnameandnotanipaddress) and not the IP address of the server its hosted on. For obvious reasons which I neglected to realize for so long the error means what it says it means the cert was not created for 8.8.8.8 the cert was created for google.com. so if you pass (8.8.8.8) for any endpoint or socket you should get failed or err common name.

hope this helps someone

@oaa97181
Copy link

@nikitalpopov @kylezinter (apologies I never got a notification for your comment) Config as below (domains changed).

upstream my-domain {
        server 127.0.0.1:4001;
}

server {
        listen          80;
        server_name     sub.example.com;
        rewrite         ^(.*)   https://$host$1 permanent;
}

server {
        listen 443 ssl; # managed by Certbot

        server_name sub.example.com;

        ssl_certificate /etc/letsencrypt/live/sub.example.com/fullchain.pem; # managed by Certbot
        ssl_certificate_key /etc/letsencrypt/live/sub.example.com/privkey.pem; # managed by Certbot
        include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
        ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

        location / {
                proxy_pass              http://my-domain;
                proxy_next_upstream     error timeout invalid_header http_500 http_502 http_503 http_504;
                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;
                proxy_set_header        X-Forwarded-Proto       $scheme;
                add_header              Front-End-Https         on;
        }

        location /socket.io/ {
                proxy_pass              http://my-domain;
                proxy_redirect          off;

                proxy_http_version      1.1;

                proxy_set_header        Upgrade                 $http_upgrade;
                proxy_set_header        Connection              "upgrade";
                proxy_set_header        Host                    $host;
                proxy_set_header        X-Real-IP               $remote_addr;
                proxy_set_header        X-Forwarded-For         $proxy_add_x_forwarded_for;
        }
}

thank you so fucking much i love you!!!!!! :DDDD

@rushdynamic
Copy link

Hi @CWFranklin, do you mind sharing a snippet of your server creation and client connection blocks? I'm having the same WebSocket is closed before the connection is established error, and my nginx config seems almost identical to yours. Thank you.

@CWFranklin
Copy link

@rushdynamic Hi, hopefully this is of some help for you. I've made a public Gist here of what I think is relevant for a full setup. I've anonymised as much as I can as it's used in a live project, but it should be coherent enough to follow: https://gist.github.com/CWFranklin/e74471360ee420c102be75e1214f018c

If there's anything else specifically you want added then let me know and I can try and sort it.

@rushdynamic
Copy link

Hi @CWFranklin, unfortunately I still haven't been able to figure out exactly what's wrong with my config yet even after going through yours, but it does feel like I'm very close. Thank you so much for taking the time to share your config though, I really appreciate it.

@codenaij
Copy link

Hi @CWFranklin

Here's mine. It doesn't seem to work tho:

server {
    listen 80;
    server_name mydomain.com;
    rewrite ^/(.*) https://mydomain.com/$1 permanent;
}


server {
     
        listen 443 ssl;
        listen [::]:443 ssl;

       

        root /var/www/html;


        #server_name _;
        server_name mydomqin.com;
        ssl_certificate /home/admin/mydomain.com.chained.crt;
        ssl_certificate_key /home/admin/mydomain.com.key;


location / {
        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;
    }


location /api {
        proxy_pass http://localhost:8000;
        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;
    }


 location /socket.io/  {
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_set_header X-NginX-Proxy false;

      proxy_pass http://localhost:8000;
      proxy_redirect off;

      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";
    }


~         

For my server.js, I had prepared it this way:

require('dotenv').config('.env')
const app = require('./app')
require('./config/db')

const port = process.env.PORT || 8000
const cors = require('cors')

const {
  loadMessages,
  sendMsg,
  setMsgToUnread,
  deleteMsg,
} = require('./utils/messageActions')

const server = app.listen(port, () =>
  console.log(`Server running on port ${port}`)
)
const io = require('socket.io')(server, {
  cors: {
    origin: '*',
  },
})

I get the error WebSocket connection to 'wss://mydomain.com:3000/ws' failed:

@asadmubashr
Copy link

same not working for me have you found any solutions?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment