Node-red server with nginx reverse proxy howto guide

Navigatable version on github
https://github.com/meeki007/node-red-server-howto-guide/blob/master/README.md

Ubuntu 20.04 - nigix - https - ssl - security - Fail2ban 403 bans, etc

Table of Contents

Introduction

This Guide is for setting up a secure Dedicated Server.


Running node-red


This guide assumes you have a running server with Ubuntu 20.04 and that you have access to terminal on the server. You can host this yourself however this guide does not address port forwarding from routers or DMZ settings and or ipv6 settings.

You need a Domain Name and the ability to point it to your IP address. Let’s Encrypt doesn’t issue certificates for bare IP addresses, only domain names. You’ll need to register a domain name in order to get a Let’s Encrypt certificate.

What your server needs: a visable IP address to the internet to include if you wish a ipv6 address.

I used a VPS (Virtual Private Server) provider. For less than $10 a month, I have a server, and I have complete root access to and can spin up Ubuntu on. I used https://servercheap.net/pricing.php I was not paid or compensated to link them. Its just what I use.


I suggest you purchase a server close to your geographical location.




If all you are going to use this server for is node-red you will only need 1gb of ram and 1 cpu however I recommend 2gb of ram and 2 cpu's




MORE! ram and cpu if you plan on running Huge flows with multiple/constantly running injections




This Guide is not for individuals who fear the CLI (command line interface). If you have issues your going to need to read and search. You can always open an issue here https://github.com/meeki007/node-red-server-howto-guide/issues and if i have time I can try to help.

Setup_Server

Bring up a terminal on the server

Creating_a_new_privileged_user_account

You should never log into your server as root. example ssh root@youripaddress
If you have not or no privileged user account exists lets create one.


If you have a privileged user account you can skip this step




In terminal:

$ adduser YourChosenNameHere

Give your new user account sudo rights by appending (-a) the sudo group (-G) to the user’s group membership:




In terminal:

$ usermod -a -G sudo YourChosenNameHere

Exit root account and login with the new privileged user account you have created

Update_Server

Update the local repositories and upgrade the operating system and installed applications by applying the latest patches.


Keep settings or files during the upgrade if asked. Follow the suggestions presented in terminal




In terminal:

$ sudo apt update && sudo apt upgrade -y


Enable_a_firewall

If you have a firewall setup you can skip this step.




Install a firewall, enable it, and configure it only to allow network traffic that you designate




In terminal:

$ sudo apt install ufw

UFW (universal firewall) denies all incoming connections and allows all outgoing connections


Enable access to SSH, HTTP, and HTTPS




In terminal:

$ sudo ufw allow ssh
$ sudo ufw allow http
$ sudo ufw allow https

Now lets start UFW




In terminal:

$ sudo ufw enable

You can see what services are allowed and denied with:




In terminal:

$ sudo ufw status

If you ever want to disable UFW, you can do so by typing:




In terminal:

$ sudo ufw disable


Install_Fail2ban

Fail2ban is an application that examines server logs looking for repeated or automated attacks. If any are found, it will alter the firewall to block the attacker’s IP address either permanently or for a specified amount of time.




In terminal:

$ sudo apt install fail2ban -y

Copy the included configuration file




In terminal:

$ sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

And restart Fail2ban




In terminal:

$ sudo service fail2ban restart


Node-red

Install follows the guide @ https://nodered.org/docs/getting-started/raspberrypi


Using the install script method works for Ubuntu

build-essential

Install possible dependency build-essential first




In terminal:

$ sudo apt install build-essential git


nod-red_script

Install nod-red and its dependencies




In terminal:

$ bash <(curl -sL https://raw.githubusercontent.com/node-red/linux-installers/master/deb/update-nodejs-and-nodered)


Autostart_on_boot

We want node-red to run automatically every time the server restarts

In terminal:
$ sudo systemctl enable nodered.service


Hosting_to_the_world

We need to setup our DNS for our domain. Install Nginx to reverse proxy so users don't have to enter a port to get to the server. Install Lets encrypt certbot to issue ssl certificates and automate the process so you never have to manually renew ssl certificates; also so users can access the site using https for security and so browsers will not complain and block access. Configure fail2ban so random people on the internet can't mess with your server.

Install_Nginx

Nginx is a web server which can also be used as a reverse proxy.


By default, Nginx is configured to start automatically when the server boots/reboots

In terminal:

$ sudo apt install nginx

Check to see if Nginx is running

In terminal:

$ systemctl status nginx

Check to see if Nginx is hosting properly.


In a web browser type your servers ip address with no port. IE http://server_ip


You should see a welcome to nginx page

DNS

You need to tie your server ipv4 and if you have one ipv6 addresses to you DNS records.
I like to use subdomains for my reverse proxy needs in Nginx.




Log into your domain name hosting provider of choice and add some records.




DNS advice is outside the scope of this tutorial. You will need to learn and or ask your provider for help if you do not understand the steps below.




Create an A record for your ipv4 server address.


Create an AAAA record for your ipv6 server address.


Now Create a CNAME record pointing a subdomain to your domain name. IE your_domain_name.com




It should look somting like this:

Hostname Type Address / Value
A server_ipv4_address
AAAA server_ipv6_address
www CNAME your_domain_name.com
node-red CNAME your_domain_name.com

The goal of all this is to create the address node-red.your_domain_name.com for users to access


You can pick a subdomain name of your choice.

Testing that your domain works with Nginx. Enter your domain name. http://your_domain_name.com

You should see a welcome to nginx page

If not DO NOT PASS GO. Fix your domain issues.

Reverse_proxy

This is used so when users goto node-red.your_domain_name.com they are directed to the node-red server running on port :1880

First create a server block file for the url , making sure to change:


server_name node-red.your_domain_name.com;


To the subdomain.domain.com that you own

In terminal:

$ sudo nano /etc/nginx/sites-available/node-red.your_domain_name.com

This will open a text editor

Paste the following into the editor, making sure to change one line:


server_name node-red.your_domain_name.com;


To the subdomain.domain.com that you own

#proxy for node-red @ port :1880
server {
    	listen 80;
	listen [::]:80;

    	server_name node-red.your_domain_name.com;

    	location = /robots.txt {
    		add_header  Content-Type  text/plain;
    		return 200 "User-agent: *\nDisallow: /\n";
    	}
    	location / {
       		proxy_pass http://127.0.0.1:1880;

		#Defines the HTTP protocol version for proxying
		#by default it it set to 1.0.
		#For Websockets and keepalive connections you need to use the version 1.1    
		proxy_http_version  1.1;

		#Sets conditions under which the response will not be taken from a cache.
		proxy_cache_bypass  $http_upgrade;

		#These header fields are required if your application is using Websockets
		proxy_set_header Upgrade $http_upgrade;

		#These header fields are required if your application is using Websockets    
		proxy_set_header Connection "upgrade";

		#The $host variable in the following order of precedence contains:
		#hostname from the request line, or hostname from the Host request header field
		#or the server name matching a request.    
		proxy_set_header Host $host;

		#Forwards the real visitor remote IP address to the proxied server
		proxy_set_header X-Real-IP $remote_addr;

		#A list containing the IP addresses of every server the client has been proxied through    
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

		#When used inside an HTTPS server block, each HTTP response from the proxied server is rewritten to HTTPS.    
		proxy_set_header X-Forwarded-Proto $scheme;

		#Defines the original host requested by the client.    
		proxy_set_header X-Forwarded-Host $host;

		#Defines the original port requested by the client.    
		proxy_set_header X-Forwarded-Port $server_port;

    	}
}

Now enable the file by creating a link from it to the sites-enabled directory, which Nginx reads from during startup:


making sure to change:


server_name node-red.your_domain_name.com;


To the subdomain.domain.com that you own

In terminal:

$ sudo ln -s /etc/nginx/sites-available/node-red.your_domain_name.com /etc/nginx/sites-enabled/

To avoid a possible hash bucket memory problem that can arise from adding additional server names, it is necessary to adjust a single value in the /etc/nginx/nginx.conf file. Open the file:

In terminal:

$ sudo nano /etc/nginx/nginx.conf

Now uncomment (remove the # symbol) from the http section line server_names_hash_bucket_size 64; so it looks like this:

http {
    ...ignore stuff here...
    server_names_hash_bucket_size 64;
    ...ignore stuff here...
}

Next, test to make sure that there are no syntax errors in any of your Nginx files:

In terminal:

$ sudo nginx -t

Restart Nginx to enable your changes:

In terminal:

$ sudo systemctl restart nginx

Later if we ever need to disable a server block file simply remove the symbolic link.

In terminal:

$ sudo rm /etc/nginx/sites-enabled/node-red.your_domain_name.com

And don't forget to restart to effect changes

In terminal:

$ sudo systemctl restart nginx

Test if the reverse proxy is working


Goto your website, non https, and make sure it works


http://node-red.your_domain_name.com

The node-red page should load. I know its tempting but do not configure it now. exit the page / close the browser.

HTTPS_SSL_certbot

Install certbot to get ssl certificates for domain


at this time cerbot not relesed for ppa package use snap

In terminal:

$ sudo snap install --beta certbot --classic


Generate the certificate for domain
making sure to change:
server_name node-red.your_domain_name.com;
To the subdomain.domain.com that you own

In terminal:

$ sudo certbot --nginx -d node-red.your_domain_name.com

If asked to redirect HTTP traffic to HTTPS, removing HTTP access:


Select 2: Redirect - Make all requests redirect to secure HTTPS access.

Test if https is working


Goto your website, https, and make sure it works


https://node-red.your_domain_name.com

The node-red page should load. I know its tempting but do not configure it now. exit the page / close the browser.

Test that lets encrypt cerbot is working and will automatically update certificates before they expire

In terminal:

$ sudo certbot renew --dry-run


Security

Setup node-reds built in login a password for the admin area, also create a fail2ban filter for 4xx failed requests.

adminAuth_user_password

First lets hash a password so we can add it to the settings.js file.

My method of choice for this is to use bcryptjs


Add to your node-red manage pallet node-red-contrib-bcrypt




Now import this flow into node-red

[{"id":"6fb8f86c.3dd218","type":"comment","z":"93ccda7b.0f6118","name":"create a key for a password","info":"","x":220,"y":120,"wires":[]},{"id":"2c990cc6.7e76e4","type":"bcrypt","z":"93ccda7b.0f6118","name":"","action":"encrypt","field":"payload","hash":"payload","rounds":"10","x":570,"y":160,"wires":[["480852c7.37515c"]]},{"id":"e203ec3d.eb8938","type":"inject","z":"93ccda7b.0f6118","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":190,"y":160,"wires":[["15919cce.3e10a3"]]},{"id":"15919cce.3e10a3","type":"change","z":"93ccda7b.0f6118","name":"Enter Password Here","rules":[{"t":"set","p":"payload","pt":"msg","to":"MyPassword","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":380,"y":160,"wires":[["2c990cc6.7e76e4"]]},{"id":"480852c7.37515c","type":"debug","z":"93ccda7b.0f6118","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":730,"y":160,"wires":[]}]

Replace MyPassword with your password!!!


Trigger the flow and check the msg.payload


copy the payload as it is your password hash you will need next




We need to setup a user name and password by editing node-red's settings.js file.


Edit the settings.js file in our current privileged_user_account /home/YourChosenNameHere/.node-red/settings.js


We can use $HOME variable to accomplish this




In terminal:

$ nano $HOME/.node-red/settings.js

This will open a text editor

Edit the adminAuth section removing forward slashes // so it looks like this:

adminAuth: {
    type: "credentials",
    users: [
        {
            username: "admin",
            password: "$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.",
            permissions: "*"
        }
    ]
}


Replace $2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN. within QUOTES with your password hash you created.

More information on this can be found at: https://nodered.org/docs/user-guide/runtime/securing-node-red



fail2ban_403_protection

Every time a user....hacker...etc tries to login to node-red with a bad user name or password it sends a 403 Forbidden error


This response code indicates that the server understood the request but refuses to authorize it




What we would like todo is ban users who have multiple failed attempts to login


That way we can keep the bad people from password spamming the server trying to gain access.




Lets block 404, 444, 403, 400 responses from the server. Feel free to read up on these codes but the gist of it is we will be blocking people trying to gain access to areas they should not be trying to get into.






Create a new fail2ban filter for 404, 444, 403, 400 responses from the server.

In terminal:

sudo nano /etc/fail2ban/filter.d/nginx-4xx.conf

This will open a text editor

Add the following to the file:

[Definition]
failregex = ^<HOST>.*"(GET|POST).*" (404|444|403|400) .*$
ignoreregex =

Now, edit your /etc/fail2ban/jail.conf, to use the new filter we created and ban after 10 failed attempts.

In terminal:

$ sudo nano /etc/fail2ban/jail.conf

This will open a text editor

Add the following to the bottom of the file:

[nginx-4xx]
enabled = true
port = http,https
logpath = %(nginx_access_log)s
maxretry = 10

Restart fail2ban

In terminal:

$ sudo service fail2ban restart

Check that the new rule is working

In terminal:

sudo fail2ban-client status nginx-4xx

Now the user will be banned for the amount of time you have set in fail2ban after 10 failed attempts

you can check this is working with the logs.

In terminal:

$ sudo cat /var/log/fail2ban.log | grep Ban

and to check in on what the nginx log is producing

In terminal:

$ sudo cat /var/log/nginx/access.log

Extras

automatic_increasing_ban_times

Many malicious actors will just take note of the fixed ban time and just keep at it every 10min. Instead of increasing the fixed time we want to increase the time for specific bad actors and not every one.

In terminal:

$ sudo nano /etc/fail2ban/jail.conf

This will open a text editor

search for and remove the # sign in front of the following lines so they look like this:

bantime.increment = true

bantime.factor = 1

bantime.formula = ban.Time * (1<<(ban.Count if ban.Count<20 else 20)) * banFact>

Restart fail2ban

In terminal:

$ sudo service fail2ban restart

END OF TUTORIAL

7 Likes

PLACE HOLDER for docker version of the guide next week

1 Like

Excellent write up. I'll be bookmarking this for later.
Thank you.

Note: I haven't tested it yet but if/when I do I'll feed back on success or any issues I encounter.

1 Like

Shouldn't this be in the Projects category?

I have moved it to the FAQs category.

1 Like

In the context of using node-red, a 404 error is occasionally seen on actual usage too. Especially when editing flows and having a typo in a url somewhere it’s what will make you notice you’ve made a mistake. Is this handled somewhere, or will those connections be dropped too? Note that I’m on mobile and have only quickly scanned the fail2ban configuration. I certainly agree with blocking it, seeing a lot of interesting attempts on my CentOS cloud server with a similar fail2ban setup, but I wonder if it’s best in a node-red context that wouldn’t purely run flows in production.

You would have to get ten 404's in ten minutes to trigger a 10min ban. I've not banned myself yet. But have made the list a few times with 3 triggers now and again.

Its a per IP address. So if you have a bunch of users all behind a NAT triggering 404's then yes you might want to increase the limit or remove 404 from the list.

Also until you get a ban it will still post the 404 for the user to see they made a mistake with a malformed url or a admin noticing that its not being served.

Remove 404 banning Example:

[Definition]
failregex = ^<HOST>.*"(GET|POST).*" (404|444|403|400) .*$
ignoreregex =

Becomes

[Definition]
failregex = ^<HOST>.*"(GET|POST).*" (444|403|400) .*$
ignoreregex =

I use 404 ban because people are always fishing the server URL for attack vectors and I'm tired of seeing all their failed attempts to access https://my-url.com/admin/foo.bar or https://my-url.com/mysql/foo.bar
Rather they get a ban than clog up the server with requests.

Good Question @afelix

All questions/solutions welcome. I make mistakes all the time. Lets make this a great Howto TEAM

1 Like

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.