Skip to content

Host Your Website: Nginx Crash Course, Part 2

Published: at 12:07 AM

Theoretical hero from Bloodborne 2, out now on PC (not really)

Self-Hosting Your Website

I went over some basics of Nginx many weeks ago in part one of this series. This time round we’re going to get funky with a reverse proxy and serve a website with an actual domain name.

I’m using Ubuntu in these examples.

Disclaimer: There Is An Easier Way

You may not want to do all this nonsense and take a look at Nginx Proxy Manager instead, which will give you a nice GUI and do lots of the heavy lifting for you. This guide is intended to serve people who like to get their hands a bit more dirty. I suffered an embarrassing amount of hours of hair-pulling so you hopefully don’t have to.

1. Install Nginx

If you haven’t already, install Nginx. You can do this with the following commands:

sudo apt-get update
sudo apt-get install nginx

2. Add Website Files

In the first example I’m going to assume you’ve already got at least a static HTML website ready to go. You’ll want to dump all your files into a directory to be served by Nginx. The default location is generally at /var/www/, like so:
/var/www/mywebsite/

But you can put them anywhere.

3. Create Server Blocks for Each Domain

For now let’s just start with one basic HTML-only site to keep this simple and we’ll call it mywebsite.com, which is a very good and original name (with the domain name currently for sale for the low, low price of $975,000). We’ll give it a random port number to listen on. I’ll use nano text editor in these files but you can use any you want.

Create the server block for our site in a text editor:
sudo nano /etc/nginx/sites-available/mywebsite.com

Pick an unused port number. If you are feeling particularly uninspired, use this tool to do it for you (and you can find lots of other fun dev tools on that site). You’ll need a port number outside of the known ports of 0-1023. I’m going to use 12233 because that’s the number it gave me.

Add this text in the text editor and save it:

server {
    listen 12233;
    server_name mywebsite.com www.mywebsite.com;

    root /var/www/mywebsite;
    index index.html;

    location / {
		    try_files $uri $uri/ =404;
    }
}

As mentioned, you don’t HAVE to save your files at /var/www/mywebsite. You can put them wherever you want. Just make sure you put the correct path in the server block and that Nginx has permission to read the directories and contents. (See ‘Tips & Troubleshooting’ for info if you’re a Linux newbie.)

Once you’ve got this file saved, you’ll also need to create a symlink to it:

sudo ln -s /etc/nginx/sites-available/mywebsite.com /etc/nginx/sites-enabled/

ln is the link command and the -s flag specifies a ‘soft’ link. Again, if you’re unfamiliar with Linux you can read more on that here.

Creating sites-available and sites-enabled allows us to have a nice, organised, modular system where we can disable sites when we don’t want them anymore (for whatever reason) while still having them available to be reenabled. You can unlink sites with the unlink command, which we’ll do in a sec.

Make sure you haven’t made any errors with your server block syntax:
sudo nginx -t

You can, and probably should, also disable the default virtual host so it doesn’t cause issues:
sudo unlink /etc/nginx/sites-enabled/default

Then reload Nginx (do this whenever you add or change a config file):
sudo systemctl reload nginx

Allow the port through the firewall using UFW:
sudo ufw allow 12233

UFW (Uncomplicated Firewall) is the default firewall config tool for Ubuntu.

4. Reverse Proxy Setup

One of the most popular uses of Nginx is that of a reverse proxy. Lots of reasons for that are out of the scope of this tutorial, including load-balancing and caching.

Because we are super cool, forward-thinking devs, we’re going to plan ahead for our burgeoning website empire and use a reverse proxy to route traffic based on the hostname. When we add another website and domain name to our roster, we can add it to our proxy config file. External traffic received on ports 80 and 443 will be redirected to the appropriate internal port based on the domain name.

Get your server’s local IP address by typing hostname -I in the Linux terminal.

Create a reverse-proxy config file the same way as before:
sudo nano /etc/nginx/sites-available/reverse-proxy.conf

Save this file:

server {
    server_name mywebsite.com www.mywebsite.com;

    location / {
      proxy_pass http://your.local.ip.address:12233;
      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;
    }
}

Activate it in your ‘enabled’ sites:

sudo ln -s /etc/nginx/sites-available/reverse-proxy.conf /etc/nginx/sites-enabled/reverse-proxy.conf

5. Point Your Domain Name to Your IP

Get your server’s public IP address. The easiest way is to simply Google ‘what’s my ip address’ but if you like looking like a hacker you can type this in your terminal:
curl -4 icanhazip.com

Log into the admin area of wherever your domain name is managed and find the DNS settings. If you are using C Panel it’s under ‘Domains > Zone Editor’ in there.

Add an ‘A’ record for your domain which points to your public IP address. Stick your IP address in that box and save it. Again, make sure it is your public IP address and not the local network one.

It can sometimes take quite a while (up to a couple of days) for DNS changes to ‘propagate’ so don’t panic. It is usually pretty quick though.

NOTE: Your ISP may not give you a static IP address by default. Some offer this service as an extra charge (hopefully a small one). If you have a dynamic IP (a different one assigned each time you reconnect your server to the internet) you will need to look into a service like NoIP.

Create a free account with noip.com and claim a hostname. Now, instead of putting your IP address as your A record, add a CNAME record with your noip.com URL instead, which will look something like myhostname.ddns.net or whatever other domain name you choose.

6. Router Port Forwarding

Log into your router (each one is different, so check out your own router admin instructions and while you’re at it you can also change your damn admin password if you haven’t already). Find out wherever ‘port forwarding’ is hiding. It might be in a section called ‘network’ or ‘firewalls’ for example.

In this section you’ll need to open ports 80 and 443. 🫨

Once you’ve done that, add port forwards for both ports which forward to your server’s local IP address. Your local IP will need to be put under what’s probably called ‘Internal IP’ with the external port being port 80, then do the same again for port 443. You should now have two port forwards, one as your.local.ip.address:80 and the other as your.local.ip.address:443.

7. Allowing HTTPS and Snagging a Free SSL Certificate

Most, if not all, modern web browsers force traffic to HTTPS instead of HTTP. HTTPS traffic is encrypted and uses a connection on port 443 by default. We’ll get that set up now along with a certificate which acts as a ‘passport’ of sorts, confirming its legit identity.

Install a package called Certbot, along with its plugin for Nginx.

sudo apt-get update
sudo apt-get install certbot python3-certbot-nginx

When we run Certbot, it’s going to do a few things.

  1. Create a free SSL certificate using Let’s Encrypt.

  2. Add some more lines to our config file. If you correctly set up the reverse-proxy.conf file it will add the code there. If you didn’t, it may have put the code in your /sites-available/mywebsite.com config instead.

  3. Set up a cron job to automatically renew the certificate when it needs to.

As I found out the hard way, if you mess up doing this bit five times you will hit their usage limits and be locked out of trying again for 24 hours or something. 🙃 Don’t make my mistake and do a dry-run of the process first.

Type this into your terminal, replacing mywebsite.com with your site’s url(s):
sudo certbot certonly --nginx -d mywebsite.com -d www.mywebsite.com --dry-run

If you are not using www in your domain name, you can skip that and just use:
sudo certbot certonly --nginx -d mywebsite.com --dry-run

If Certbot was successful, go ahead and do it without the dry-run flag:
sudo certbot --nginx -d mywebsite.com -d www.mywebsite.com

Let’s Encrypt certificates are valid for 90 days. You can do a dry-run test of the renewal process with:
sudo certbot renew --dry-run

We want our reverse proxy to handle the SSL/TLS termination (which is another benefit of a reverse proxy), so the SSL certificates will be installed on the reverse proxy server. If all went well with the above then that’s what’s just happened!

You can check the contents of your proxy config to make sure Certbot has added its stuff there:
sudo cat /etc/nginx/sites-available/mywebsite.com

It should look something like this but obviously with your IP address, port and domain names instead:

server {
    server_name mywebsite.com www.mywebsite.com;

    location / {
      proxy_pass http://192.168.1.123:12233; # your IP and port as you entered previously
      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;
    }

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/mywebsite.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/mywebsite.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
}

server {
    if ($host = mywebsite.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

    server_name mywebsite.com;
    listen 80;
    return 404; # managed by Certbot
}

Your config should’ve already been reloaded in the process but just for good measure you could always do that yourself:
sudo systemctl reload nginx

8. Visit Your Shiny New Website

In your browser of choice, go to whatever your domain name actually is and see your website in action!

That is, hopefully, it. Step six, port forwarding, only needs to be done once. If you have several sites and domains, you’ll need to repeat all the other steps for each one, assigning different port numbers. You can keep adding sites to the reverse-proxy.conf file or add new config files. It’s up to you how to organise it.

Tips & Troubleshooting

THE PORT IS ALREADY IN USE 😡

Choose a different port, then.

Or…

Find the process running on a port with fuser [port number]/tcp

eg. fuser 12233/tcp

Kill whatever process is on that port with fuser -k [port number]/tcp

Alternatively…

List any process listening to the port 8080: lsof -i:8080

Kill any process listening to the port 8080: kill $(lsof -t -i:8080)

ONLY THE ‘WELCOME TO NGINX’ DEFAULT WEB PAGE SHOWS UP. WHAT THE HECK

If the ‘Welcome to Nginx’ page comes up it’s because something is serving the default at /var/www/html

Did you forget to add your site to the reverse-proxy.conf file?

HOW TO CHECK IF MY SITE IS ACTUALLY BEING SERVED

There’s a couple of ways to do this.

You can use the curl command to see what data comes back:
curl mywebsite.com

You can get header information with the -I flag:
curl -I mywebsite.com

You could also put the local IP address and port in the browser instead:
your.local.ip.address:port
eg. 192.168.1.123:12233

I GET A 404 ERROR WHEN I PUT MY DOMAIN IN THE BROWSER BUT I KNOW MY FILES ARE THERE. ARGH

There may be an issue with directory permissions.

I had this issue when I decided to serve my files from a directory which wasn’t /var/www/*

You need to give Nginx permission to read the directory and its contents. Use whatever method suits your security needs. The Nginx user is, by default, under the name www-data. You can add www-data to a group which has access to the necessary files, or another way is to use ACL (Access Control Lists).

Install ACL with sudo apt-get install acl

# Grant read and execute permissions to the Nginx user (www-data) for the directory
sudo setfacl -R -m u:www-data:rx /path/to/directory

# Apply the ACLs recursively to all files and subdirectories within the directory
sudo setfacl -R -m default:u:www-data:rx /path/to/directory

# See active ACLs
sudo getfacl /path/to/directory

So if you have your site located in the directory /home/yourusername/www, you need to enter:

sudo setfacl -R -m u:www-data:rx /home/yourusername/www

sudo setfacl -R -m default:u:www-data:rx /home/yourusername/www

WHERE ARE THE NGINX LOG FILES?

Default locations for Nginx and Certbot log files:

Nginx access log : /var/log/nginx/access.log

Nginx error log: /var/log/nginx/error.log

Certbot log: /var/log/letsencrypt/

View them with sudo cat or sudo tail /path/to/log

I HAVE A PROBLEM WITH LET’S ENCRYPT PERMISSIONS

Certbot typically runs as root, so it should already have the necessary permissions to access your webroot directory and write the challenge files for domain validation. If, however, you have issues at the Certbot stage, you can try setting permissions using chmod and chown.

sudo chmod 644 /etc/letsencrypt/live/mywebsite.com/fullchain.pem
sudo chmod 600 /etc/letsencrypt/live/mywebsite.com/privkey.pem
sudo chown www-data:www-data /etc/letsencrypt/live/mywebsite.com/fullchain.pem
sudo chown www-data:www-data /etc/letsencrypt/live/mywebsite.com/privkey.pem

I ASSUME THERE IS A DEFAULT CONFIG FILE ALREADY

You are correct. It typically lives at /etc/nginx/conf.d/default.conf

HOW DO I CHANGE THE PORTS IN MY FIREWALL?

You can check the status of allowed/denied ports with: ufw status

Allow a port, let’s say 8080: sudo ufw allow 8080

Deny a port: sudo ufw deny 8080

To tidy up your mess and delete an existing rule, prefix the original rule with ‘delete’: sudo ufw delete deny 8080

HOW CAN I CHECK IF MY DNS HAS PROPAGATED ROUND THE WORLD? 🌍

Go to www.whatsmydns.net and put your domain name in the search box. If your public IP address shows up with lots of pleasing green ticks, then it’s working.

SOMETHING ELSE DOESN’T WORK

If in doubt, restart Nginx: sudo systemctl restart nginx

HOW DO I STOP AND START NGINX?

sudo systemctl stop nginx
sudo systemctl start nginx
Check its status: sudo systemctl status nginx

I WANT TO SEE THE CERTBOT CRON JOB, BECAUSE I AM A CRON JOB FAN 🤖

You can check your cron jobs with this: ls /etc/cron.d

You should see certbot listed there. You can check the file contents with this:
cat /etc/cron.d/certbot

See all your system’s timers with this:
systemctl list-timers --all

EXTRA SECURITY??? RATE LIMITING????? GIVE ME MORE STUFF

To come in part 3, due… whenever I’ve worked it all out. 🫠🫠🫠