Protecting your NodeJS app with Nginx
Let's say you are developing a NodeJS application. It runs great and you are very happy. Did you know it's not very good practice to serve your app directly to your visitors?
Especially because if your app is serving pages though ports 80 or 443, it needs privileged access to your system. It means than if your app gets compromised because of a security hole, then your whole system gets compromised.
Nginx is a very fast, lightweight web server. You can put Nginx between your app and your visitors. Visitors will query Nginx, Nginx will forward the queries to your nodeJS app, and forward the response of your app back to your visitors:
Visitors <=> Nginx <=> NodeJS app
That way, Nginx acts as a reverse proxy to your app.
Nginx has privileges to the system in order to open ports 80 and 443 (the standard HTTP and HTTPS ports), but it does nothing else than forwarding queries. It means that it won't get hacked, and won't endanger your whole system.
And now that your nodeJS app does not need privileged system access any more, you can just give it restricted user access. That way, if your app gets hacked, your system will still be safe.
There are other advantages to this:
- Nginx can act as a secure filter
- You remove some complicated stuff from your app and put them in Nginx where they are more easily managed. Typically: SSL, DDoS protection, load balancing, ...
- You can very easily host multiple apps on different domains behind a single Nginx instance: virtual hosting gets very easy to set up with Nginx.
You can watch this very good tutorial on how to set up Nginx in front of a NodeJS app on a EC2 AWS server:
Installing Nginx on Ubuntu or Debian
In case you don't have nginx already on your server, installing it is quite straightforward:
sudo apt install -y nginx
sudo systemctl enable nginx
sudo systemctl start nginx
Setting SSL in Nginx with CertBot
Now that we have a minimal Nginx reverse proxy serving our app via port 80 (HTTP), we will set HTTPS up, so communication between our server and our visitors is encrypted.
The good thing is that we don't need to implement this complicated SSL stuff into our nodeJS app: our app stays as simple as possible.
Have your own domain
First you need to have a domain name, then set the DNS correctly so that it points to your server. This is out of the scope of this tutorial.
Install CertBot
After, you need to install CertBot. There are three ways to install CertBot on Ubuntu or Debian: apt, snap, or pip.
The classic way is to install it with apt. That is was you'll find in most of the tutorials. Unfortunately:
- the apt package is often many versions behind
- certbot is dumping support for apt, so you'll probably get some error if you try to install certbot via apt from now on
The best way is to use pip:
// some-commands.sh
sudo apt install python3-pip # install pip in case you don't have it
sudo -H pip3 install certbot-nginx # install certbot with nginx plugin
Generate your SSL certificates with CertBot
Certbot will do everything for you: create the SSL certificates for your domain and configure Nginx.
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
You can now access your app via SSL.
Using websockets with Nginx
So far so good. Now, if your application uses web sockets, you can also use Nginx to act as a web socket reverse proxy. Read the details here: Nginx as a WebSocket Proxy
What's great is that, if you have configured https like in the previous paragraph, your websocket connection will also be encrypted without any more fiddling.
Tip: if you want to test your WebSocket connection from the command line, install the CLI websocket client called wscat with npm install -g wscat
then connect with:
wscat -c wss://mydomain.com/ws-endpoint
Protecting your application with HTTP basic auth
In the end, you'll probably use something more advanced like an SSO, but while you are developing your app, it might be useful to first quickly protect it with basic HTTP auth.
First, make sure that you have the apache tools needed to create an htpasswd file that will store your users and their passwords:
sudo apt install apache2-utils
Then create that file with:
sudo htpasswd -c /etc/nginx/htpasswd my-user1
If you want to add another user, just use:
sudo htpasswd /etc/nginx/htpasswd my-user2
Forget the -c
flag, because your htpasswd file is already created.
Finally, change your nginx configuration file to add these two lines:
auth_basic "My protected domain";
auth_basic_user_file /etc/nginx/htpasswd;
Put these lines inside the server {}
section if you want to apply it to your whole server, or in a location /protected {}
section, if you only want to protect a specific section of your site.
Don't forget to block your app from the outside world
Please, once you are all happy that your application is viewable via https on your domain, don't forget to close direct access to your app. Imagine it runs on port 3000, port on which your nginx reverse proxy is querying it, make sure that nobody can directly access your app via http://your.domain:3000
If you are on AWS, go to the security settings to make sure port 3000 is closed from the outside world.
If you aren't, then use ufw (uncomplicated firewall) to block port 3000:
// block-port-3000.sh
# check ufw is running
sudo ufw status
# if it isn't, launch it
sudo ufw enable
# now block port 3000 from the outside world
sudo ufw deny 3000/tcp
That's it, nobody from the outside world can access your app directly any more. Of course, do the same with any other port your application requires, in case you use several of them.
So, with nginx, we've quickly secured our app, served it with SSL, allowed secure websocket connections, and protected it with basic auth. With only a few keystrokes.