We have nginx running two servers (port 80 and 443). These proxy_pass our upstreams:
upstream app_nodes {
ip_hash;
server 127.0.0.1:3000;
server 127.0.0.1:3001;
}
upstream app_nodes_https {
ip_hash;
server 127.0.0.1:8000;
server 127.0.0.1:8001;
}
For port 80, this is fine. However, for 443 this fails because we don't have ssl certs defined within nginx. We need our node.js app (listening on port 8000/8001) to handle the certificates to support many domains dynamically.
Is there a way to have nginx simply proxy our upstream servers and let them handle ssl?
Thank you
EDIT: Here's our server block for 443
server {
listen 443;
gzip on;
gzip_types text/plain application/json application/ocet-stream;
location / {
proxy_pass https://app_nodes_https;
add_header X-Upstream $upstream_addr;
add_header X-Real-IP $remote_addr;
include /etc/nginx/proxy_params;
}
}
Doing nginx -t actually gives the error that https protocol requires SSL support
The solution is to use streams:
stream {
upstream https_stream {
hash $remote_addr;
server 127.0.0.1:8000;
server 127.0.0.1:8001;
}
server {
listen 443;
proxy_pass https_stream;
}
}
This is assuming your running your app instances on 8000/8001.
Related
I have a nodejs app that functions as a webserver listening to port 5050
I've created certificates and configured NGINX which works for normal https calls to the standard port (https://x.x/)
If I make a call to port 5050 with a normal http://x.x:5050 call it also works, but with an https://x.x:5050/conf call I get: This site can’t provide a secure connection
Below the NGINX config file:
(The names of the website are changed)
server {
root /var/www/x.x/html;
index index.html index.htm index.nginx-debian.html;
server_name x.x www.x.x;
location / {
try_files $uri $uri/ =404;
}
location /conf {
proxy_pass http://localhost:5050;
try_files $uri $uri/ =404;
}
location /wh {
proxy_pass http://localhost:5050;
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;
}
listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/x.x/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/x.x/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
}
What am I doing wrong here?
You configured nginx to serve as a reverse proxy, forwarding incoming requests from https://example.com/whatever to http://localhost:5050/whatever. You said you did that correctly and it works. Good. (Getting that working is a notorious pain in the xxx neck.)
You did not configure nginx to listen on port 5050. Nor should you; that's the port it uses to pass along requests to your nodejs program. You cannot forward requests from port 5050 to port 5050. If you try to have nodejs and nginx both listen to port 5050, one of them will get an EADRINUSE error when you start your servers.
Your nodejs program listens for http requests, not https requests, on port 5050. You can't easily make it listen for both http and https on the same port. Your nodejs program, when behind nginx, should not contain any https server, only http. (You're making nginx do the hard crypto work to handle https, and letting nodejs handle your requests.)
Nor do you want your nodejs program to listen directly for http-only requests from outside your server. Because cybercreeps.
If you can block access to port 5050 from anywhere except localhost, you can declare victory on your task of configuring your server. You can do this by using
server.listen({
host: 'localhost',
port: 5050, ...
});```
in your nodejs program. Or you can configure your server's firewall to block incoming requests on any ports except https (and ssh, so you can manage it). Digital Ocean has a useful tutorial on this point.
I have a Nginx server handling http request and doing proxy pass to some node servers upstream, if the domain name match one of the enabled sites, all packets are redirected to one node server, only if the channel is SSL, otherwise 301 to the https version:
server {
listen 80;
server_name something.com
return 301 https://$host$request_uri;
}
server {
listen 433;
server_name something.com;
ssl_certificate /etc/nginx/cert.crt;
ssl_certificate_key /etc/nginx/cert.key;
ssl on;
ssl_session_cache builtin:1000 shared:SSL:10m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4;
ssl_prefer_server_ciphers on;
location / {
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 true;
proxy_pass http://127.0.0.1:3000/;
proxy_redirect off;
}
}
All that works, but the certificates management, the SSL handshake and so are made by Nginx. I will like to have each node server upstream to manage their own SSL preferences so I don't depend on Nginx to do this. My node servers already support https requests but I don't understand if it is possible to tell Nginx:
Listen to 80, if something comes do a 301 to the https version of it.
Listen to 433, don't worry for SSL, just proxy pass everything to localhost:3000
And the node server listening to port 3000 handles SSL
Listen to 433, don't worry for SSL, just proxy pass everything to localhost:3000
No, not with nginx, you will have to use port forwarding for that.
nginx would either have to use some SSL key and possibly proxy the traffic to some Node app using SSL, this will mean that both Node and nginx would have to manage their own SSL keys (nginx for the client-nginx connection and Node app for the nginx-nodeApp connection).
Or nginx could use HTTP without SSL to proxy the request to Node that uses SSL, and this will mean that the client-nginx connection would be insecure and only the nginx-NodeApp connection would be secure. Also it would mean that https://www.example.com/ would not work - though http://www.example.com:443/ would.
If you want Node to handle the SSL keys and not the reverse proxy (as it is usually done) then you would have to use port forwarding on the TCP/IP level to pass the traffic to the Node app, without using a reverse proxy (nginx) at all.
Usually a reverse proxy is used so that the apps wouldn't have to handle the SSL keys used for client connections (among other things). If you want the Node apps to use the SSL keys and not the reverse proxy then you should reconsider using a reverse proxy in the first place.
My Nginx default file looks like this:
server {
listen 80;
server_name humanfox.com www.humanfox.com;
rewrite ^/(.*)$ https://www.humanfox.com$1 permanent;
}
server {
listen 443 ssl spdy;
server_name humanfox.com www.humanfox.com;
ssl on;
ssl_certificate /root/ca3/www.humanfox.com.crt;
ssl_certificate_key /root/ca3/humanfox.com.key;
access_log /var/log/nginx/humanfox.com.access.log;
error_log /var/log/nginx/humanfox.com.error.log;
rewrite ^/(.*)$ https://www.humanfox.com$1 permanent;
}
Now, Nginx is running properly but when I try to run my nodejs server on port 443(https) it says EADDR already in use.
When I kill the port to use my nodejs server instead, it also kills Nginx and Nginx stops working.
How do I run my nodejs server on 443 ensuring nginx doesn't close.
You can't run nodejs on port 443 and nginx to serve ssl(443) at the same time. You can do this by configuring nginx as a reverse proxy for nodejs.
Let say you are running nodejs in port 3000.
const http = require('http');
http.createServer((req,res) => {
res.writeHead(200, {"Content-Type":"plain/html"});
res.end('Node is Running');
}).listen(3000);
your nginx config should be:
server {
listen 443 ssl spdy;
server_name humanfox.com www.humanfox.com;
ssl on;
ssl_certificate /root/ca3/www.humanfox.com.crt;
ssl_certificate_key /root/ca3/humanfox.com.key;
access_log /var/log/nginx/humanfox.com.access.log;
error_log /var/log/nginx/humanfox.com.error.log;
location / {
proxy_set_header X-Forwarder-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://127.0.0.1:3000;
proxy_redirect off;
}
}
Hope it Helps.
You will not be able listen on the same port as nginx is already listening on.
What you can do is listen on some different port and configure nginx as a reverse proxy to connect to your node process. That way from the point of view of external users it will look exactly how you want - the Node app will be accessible on the 443 port - but there would be no conflict of ports.
See this answers for examples on how to do that:
Where do I put my Node JS app so it is accessible via the main website?
How do I get the server_name in nginx to use as a server variable in node
Configuring HTTPS for Express and Nginx
I'm writing web socket project, everything is working like expected(locally), I using:
NGINX as a WebSockets Proxy
NODEJS as a backend server
WS as websocket module: ws
NGINX configuration:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream backend_cluster {
server 127.0.0.1:5050;
}
# Only retry if there was a communication error, not a timeout.
proxy_next_upstream error;
server {
access_log /code/logs/access.log;
error_log /code/logs/error.log info;
listen 80;
listen 443 ssl;
server_name mydomain;
root html;
ssl_certificate /code/certs/sslCert.crt;
ssl_certificate_key /code/certs/sslKey.key;
ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2; # basically same as apache [all -SSLv2]
ssl_ciphers HIGH:MEDIUM:!aNULL:!MD5;
location /websocket/ws {
proxy_pass http://backend_cluster;
proxy_http_version 1.1;
proxy_redirect off ;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Like I mentioned this is working just fine locally and in one machine in development environments, the issue I'm worry about is when we will go to production, in production environments will have more that one nodejs server.
In production the configuration for nginx will be something like:
upstream backend_cluster {
server domain1:5050;
server domain2:5050;
}
So I don't know how NGINX solves the issue for stickiness, meaning how I know that after the 'HANDSHAKE/upgrade' is done in one server, how it will know to continue working with the same server, is there a way to tell NGINX to stick to the same server?
I hope I make my self clear.
Thanks in advanced
Use this configuration:
upstream backend_cluster {
ip_hash;
server domain1:5050;
server domain2:5050;
}
clody69's answer is pretty standard. However I prefer using the following configuration for 2 reasons :
Users connecting from the same public IP should be connecting to 2 different servers if needed. ip_hash enforces 1 server per public IP.
If user 1 is maxing out server 1's performance I want him/her to be able to use the application smoothly if he/she opens another tab. ip_hash doesn't allow that.
upstream backend_cluster {
hash $content_type;
server domain1:5050;
server domain2:5050;
}
How can we configure a server to serve http://domain1.com using Meteor.js and http://domain2.com using nginx/apache?
You could use a node-http-proxy script to do this or nginx.
A sample node-http-proxy script. Be sure to use the caronte branch will allows websockets to work with meteor without falling to long polling:
Sample node.js script
var httpProxy = require('http-proxy');
httpProxy.createServer({
router: {
'domain1.com': 'localhost:3000' //Meteor port & host
'domain2.com': 'localhost:8000' //Apache port & host
}
}).listen(80);
So the above would run on port 80. You would run meteor on port 3000 and apache/nginx on port 8000.
The proxy would check the domain hostname and if its domain1.com it would act as a transparent proxy to localhost:3000
Another other way to do this is let nginx handle the proxying and using virtual hosts to separate the traffic.
You'll need nginx 1.4.3 or newer to proxy websockets, and the following config will do it:
/etc/nginx/conf.d/upgrade.conf
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
/etc/nginx/sites-enabled/meteor
server {
server_name domain1.com;
# add_header X-Powered-By Meteor;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
and your nginx config for the Apache site would be the same as usual, but with server_name domain2.com; or whatever you want to name it.