Web Programming

How to Host a Static Website on a VPS with Nginx

By REM Web Development4 min read

This tutorial follows on from Going Live With Your Website. You should be comfortable typing commands in a terminal. If you have never used SSH before, set aside an extra half hour.

In How The Web Works we described a web server as "a computer that sends files to browsers when they ask for them". Renting a VPS (virtual private server) is the most direct way to see that for yourself: you get a small Linux machine on the internet, install a web server on it, copy your HTML, CSS and JavaScript files over, and point a domain name at it. This post walks through that whole process for a static site, which means plain files with no database.

VPS or shared hosting?

You don't need a VPS to publish a static site. Shared hosting, or a static host, will serve HTML files just fine, and someone else handles security updates. A VPS is worth it when you want to learn how servers really work, run something besides static files (a Node.js API, a Python WebSocket server, a database, a game server), or have full control over the configuration. The trade-off is that keeping it patched becomes your job. Plusweb explains the differences in more detail in VPS vs Web Hosting: Which to Choose.

For a small static site, the smallest plan almost any provider offers is enough: 1 CPU core, 1 GB of RAM and Ubuntu.

1. First login and basic security

Your provider gives you an IP address and a root password or SSH key. Log in, update everything, and create a normal user so you don't work as root:

ssh root@203.0.113.10

apt update && apt upgrade -y
adduser deploy
usermod -aG sudo deploy

# allow only SSH and web traffic through the firewall
ufw allow OpenSSH
ufw allow 'Nginx Full'
ufw enable

From here on, log in as deploy (ssh deploy@203.0.113.10). Using SSH keys instead of passwords is strongly recommended. Your provider's docs or ssh-copy-id will set that up in a minute.

2. Install Nginx and add your site

sudo apt install -y nginx
sudo mkdir -p /var/www/example.com
sudo chown -R deploy:deploy /var/www/example.com

Now tell Nginx about the site. Create /etc/nginx/sites-available/example.com:

server {
    listen 80;
    server_name example.com www.example.com;

    root /var/www/example.com;
    index index.html;

    # serve the file, then the folder's index.html, otherwise 404
    location / {
        try_files $uri $uri/ =404;
    }

    error_page 404 /404.html;

    # long cache for fingerprinted assets, short for HTML
    location ~* \.(css|js|png|jpg|jpeg|gif|svg|webp|woff2?)$ {
        expires 30d;
        add_header Cache-Control "public";
    }
}
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo nginx -t          # always test the config before reloading
sudo systemctl reload nginx

Copy any index.html into /var/www/example.com and visit http://203.0.113.10. You should see your page. If the browser shows ERR_CONNECTION_REFUSED instead, the firewall or Nginx isn't running. Check with sudo systemctl status nginx.

3. Point your domain at the server

In your domain's DNS settings, create two A records: one for example.com and one for www, both pointing at the VPS's IP address. DNS changes can take anywhere from a few minutes to a few hours to reach everyone. You can check progress with:

dig +short example.com
dig +short www.example.com @8.8.8.8

4. Add HTTPS

Once the domain resolves to your server, get a free certificate from Let's Encrypt. Certbot edits the Nginx config for you and renews the certificate automatically:

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com

Choose the option to redirect HTTP to HTTPS. Then pick one host as your canonical address (with or without www) and redirect the other to it in a single hop. Why that matters is explained in Canonical URLs Explained and 301 Redirects Without Redirect Chains. Those examples use Apache, but the rules are the same in Nginx.

5. Deploy with one command

You could upload files with an FTP client every time, but rsync over SSH is faster and safer. It only sends files that changed, and --delete removes files you deleted locally:

# from your project folder, after building the site into dist/
rsync -avz --delete dist/ deploy@203.0.113.10:/var/www/example.com/

Put that line in a deploy script in your package.json (see Getting Started With npm) and publishing becomes npm run build && npm run deploy.

Keeping it running

  • Turn on automatic security updates: sudo apt install unattended-upgrades.
  • Check the error log when something looks wrong: sudo tail -f /var/log/nginx/error.log.
  • Take provider snapshots before big changes. They are the quickest way back from a mistake.
  • If you later put an app server behind Nginx, read Forward and Reverse Proxy Servers. That setup is a reverse proxy.

That's the whole stack behind a "real" website: a Linux machine, a web server, DNS, a certificate and a way to copy files. Everything else (frameworks, CMSs, hosting dashboards) is built on those same pieces.