Updated
—
8 min read
Using Nginx as a reverse proxy is common approach if you want to host multiple Node.js apps on the same VPS.
I was familiar with using ufw
for my firewall, but on a new instance I'm running bitnami, which uses iptables
instead.
So here's a quick guide on how to set up iptables rules for Nginx reverse proxy + Node.js apps.
Note: I'm not an iptables expert, so I'm not sure if these rules are the best way to do it, but they work for me.
Warning: Messing around with iptables can be dangerous. If you're not careful, you can lock yourself out of your server. I recommend creating a backup of your server and ensuring if you do get locked out you can still access your server via the console.
Here are the rules I'm using:
bash# Make a backup of your current iptables rules
sudo iptables-save > ~/iptables-backup
# IMPORTANT STEP: Allow inbound traffic to SSH. Otherwise you'll get locked out of your server.
sudo iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT
# Allow outbound SSH (e.g. for Git pull)
sudo iptables -A OUTPUT -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT
sudo iptables -A OUTPUT -p tcp --dport 22 -m state --state NEW,ESTABLISHED -j ACCEPT
# SAFELY set default policies to DROP (i.e. reject all traffic unless explicitly allowed)
sudo iptables -P INPUT DROP
sudo iptables -P FORWARD DROP
sudo iptables -P OUTPUT DROP
# Allow HTTP and HTTPS traffic
sudo iptables -A INPUT -p tcp --dport 80 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT
sudo iptables -A OUTPUT -p tcp --sport 80 -m conntrack --ctstate ESTABLISHED -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT
sudo iptables -A OUTPUT -p tcp --sport 443 -m conntrack --ctstate ESTABLISHED -j ACCEPT
# Allow established and related connections (to maintain ongoing connections)
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED -j ACCEPT
# Allow loopback traffic (for internal communication between apps)
sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A OUTPUT -o lo -j ACCEPT
# Allow inbound and outbound DNS traffic (e.g. for communicating with external APIs)
sudo iptables -A OUTPUT -p udp --dport 53 -m state --state NEW,ESTABLISHED -j ACCEPT
sudo iptables -A OUTPUT -p tcp --dport 53 -m state --state NEW,ESTABLISHED -j ACCEPT
sudo iptables -A INPUT -p udp --sport 53 -m state --state ESTABLISHED -j ACCEPT
sudo iptables -A INPUT -p tcp --sport 53 -m state --state ESTABLISHED -j ACCEPT
# Allow outbound git traffic (git protocol uses port 9418)
sudo iptables -A OUTPUT -p tcp --dport 9418 -m state --state NEW,ESTABLISHED -j ACCEPT
# Allow related inbound git traffic (git protocol uses port 9418)
sudo iptables -A INPUT -p tcp --sport 9418 -m state --state ESTABLISHED -j ACCEPT
# Persist IPTABLES rules (this will give option to save)
sudo apt update
sudo apt-get install iptables-persistent
# If you make changes later, manually save rules
sudo iptables-save > /etc/iptables/rules.v4 # IPv4 rules
sudo ip6tables-save > /etc/iptables/rules.v6 # IPv6 rules (if applicable)
Here's a quick explanation of what each rule does:
bashsudo iptables-save > ~/iptables-backup
This saves a backup of your current iptables rules. You can restore your rules by running:
bashsudo iptables-restore < ~/iptables-backup
bashsudo iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT
This allows inbound traffic to SSH. This is very important, otherwise you may get locked out of your server while making changes.
bashsudo iptables -A OUTPUT -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT
sudo iptables -A OUTPUT -p tcp --dport 22 -m state --state NEW,ESTABLISHED -j ACCEPT
This allows outbound SSH traffic, which is necessary if you want to pull code from a Git repo.
bashsudo iptables -P INPUT DROP
sudo iptables -P FORWARD DROP
sudo iptables -P OUTPUT DROP
Now that SSH is allowed, we can safely set the default policies to DROP. This means that all traffic will be rejected unless explicitly allowed.
bashsudo iptables -A INPUT -p tcp --dport 80 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT
sudo iptables -A OUTPUT -p tcp --sport 80 -m conntrack --ctstate ESTABLISHED -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT
sudo iptables -A OUTPUT -p tcp --sport 443 -m conntrack --ctstate ESTABLISHED -j ACCEPT
This allows inbound and outbound HTTP (port 80
) and HTTPS (port 443
) traffic.
When using an Nginx reverse proxy, Nginx will listen on ports 80
and 443
and forward traffic to your Node.js apps
(running on different ports, like 3000
, 4000
, etc.).
Since we're using a reverse proxy, we don't need to allow inbound traffic to the Node.js apps themselves.
However, if your setup is different, and you want to allow inbound traffic to your Node.js apps, you can do so by adding rules for each port, e.g.:
bashsudo iptables -A INPUT -p tcp --dport 3000 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT
sudo iptables -A OUTPUT -p tcp --sport 3000 -m conntrack --ctstate ESTABLISHED -j ACCEPT
bashsudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED -j ACCEPT
This allows established and related connections. This is necessary to maintain ongoing connections.
bashsudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A OUTPUT -o lo -j ACCEPT
The loopback (lo
) rules allow internal communication within the server itself, such as a service connecting to a local
database or applications communicating via the loopback network interface (localhost). For example, a Node.js app can
connect to a PostgreSQL database running on the same server via localhost.
bashsudo iptables -A OUTPUT -p udp --dport 53 -m state --state NEW,ESTABLISHED -j ACCEPT
sudo iptables -A OUTPUT -p tcp --dport 53 -m state --state NEW,ESTABLISHED -j ACCEPT
sudo iptables -A INPUT -p udp --sport 53 -m state --state ESTABLISHED -j ACCEPT
sudo iptables -A INPUT -p tcp --sport 53 -m state --state ESTABLISHED -j ACCEPT
This step allows inbound and outbound DNS traffic. This is necessary for communicating with external APIs because the
server needs to be able to resolve the domain name to an IP address. For example, if you're using a third-party API like
Stripe, your server needs to be able to resolve api.stripe.com
to an IP address.
DNS uses both TCP and UDP, so we need to allow both. DNS requests are typically sent from a random high port, so we
need to specify the source port (--sport
) for inbound traffic to be port 53. Similarly, for outbound traffic, the
destination port
(--dport
) needs to be port 53.
bashsudo iptables -A OUTPUT -p tcp --dport 9418 -m state --state NEW,ESTABLISHED -j ACCEPT
sudo iptables -A INPUT -p tcp --sport 9418 -m state --state ESTABLISHED -j ACCEPT
Git protocol uses port 9418, so we need to allow outbound and inbound traffic on this port. I'm not entirely sure if this is necessary if you're using SSH to pull code from a Git repo, but I added it just in case.
bashsudo apt update
sudo apt-get install iptables-persistent
# If you make changes later, manually save rules
sudo iptables-save > /etc/iptables/rules.v4 # IPv4 rules
sudo ip6tables-save > /etc/iptables/rules.v6 # IPv6 rules (if applicable)
Finally, we need to persist the iptables rules. This will give you the option to save the rules when you install
iptables-persistent
. If you make changes later, you can manually save the rules.
I hope sharing my iptables setup helps! If you have any questions or ƒeedback, feel free to let me know.
I'm still learning about iptables, so if you have any suggestions on how to improve these rules, I'd love to hear them.
Meet the Author
Ryan Chiang
Hello, I'm Ryan. I build things and write about them. This is my blog of my learnings, tutorials, and whatever else I feel like writing about.
See what I'm building →.
Thanks for reading! If you want a heads up when I write a new blog post, you can subscribe below: