[ Back to log ]

How to host a static site on a VPS with Nginx

DevOps

Prerequisites

  • A Linux VPS (Ubuntu or Debian works fine).
  • A domain name pointing to your server’s IP address.
  • Basic terminal knowledge.

1. Connect and create a non-root user

You shouldn’t run your server as root. It’s too easy to break things or leave security holes. SSH into your server as root first:

ssh root@your_server_ip

Create a new user (I’ll use deployer) and give them admin rights:

adduser deployer
usermod -aG sudo deployer

Switch to your new user:

su - deployer

Note: For future logins, SSH directly as this user (ssh deployer@your_server_ip).

Before you go further, set this user up with an SSH key and turn off password logins. A password-only account is still something bots can guess at. Generate a key on your local machine, copy it over with ssh-copy-id deployer@your_server_ip, then set PasswordAuthentication no in /etc/ssh/sshd_config and restart SSH. Do it now, while the box is empty and a lockout costs you nothing. See full guide here.

2. Update your server

Always update your package lists before installing new software:

sudo apt update && sudo apt upgrade -y

3. Configure the firewall

VPS providers often assign IPs that get constantly scanned by bots. You need a firewall. UFW (Uncomplicated Firewall) is the easiest way to handle this. Open up SSH, HTTP, and HTTPS:

sudo apt install ufw -y
sudo ufw limit 22
sudo ufw allow 80
sudo ufw allow 443
sudo ufw enable
sudo ufw status

limit on port 22 rate-limits SSH, so an IP that keeps retrying gets dropped. And leave UFW logging on. Those logs are how you’d notice someone hammering your ports.

4. Install Nginx, Certbot, and Git

Now we need the actual web server, the tool to get our free SSL certificate, and Git to pull down our code.

sudo apt install nginx certbot python3-certbot-nginx git -y

5. Fetch your website files with Git

Create a parent directory for your website. Replace example.com with your actual domain.

sudo mkdir -p /var/www/example.com

Change the ownership of the directory to your current user. This lets you clone your repository without using sudo:

sudo chown -R $USER:$USER /var/www/example.com

Clone your static site repository into a static folder:

git clone https://github.com/yourusername/your-static-site-repo.git /var/www/example.com/static

(If you just want to test things out without a repo, you can make an index.html file manually instead:)

mkdir -p /var/www/example.com/static
echo "<h1>Hello World from my VPS!</h1>" > /var/www/example.com/static/index.html

6. Configure Nginx

Create a new Nginx server block for your domain:

sudo nano /etc/nginx/sites-available/example.com

Add this configuration. Make sure to swap in your actual domain and file paths:

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

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

    server_tokens off;

    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

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

    location ~ /\. {
        deny all;
    }

    access_log /var/log/nginx/example.com.access.log;
    error_log /var/log/nginx/example.com.error.log;
}

server_tokens off stops Nginx from printing its exact version, one less thing handed to anyone scanning for a known CVE. The add_header lines block clickjacking, stop content-type guessing, and trim the referrer. The location ~ /\. block denies dotfiles, so a stray .git or .env in your web root never gets served. The always keyword isn’t optional: without it, Nginx skips those headers on error pages, exactly where you don’t want information leaking.

Save and exit (in nano, press Ctrl+O, Enter, then Ctrl+X).

7. Enable the site and reload Nginx

Create a symlink to enable the site:

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

Test your Nginx config for typos:

sudo nginx -t

If it passes, reload Nginx:

sudo systemctl reload nginx

8. Secure your site with SSL

HTTPS isn’t optional anymore. Browsers will flag your site if you don’t have it. Certbot makes this painless:

sudo certbot --nginx -d example.com -d www.example.com

Certbot will ask for an email address and prompt you to agree to the terms. It automatically updates your Nginx config to use the certificate and forces HTTP traffic to HTTPS.

Now that you’re on HTTPS, add one more header so browsers refuse to fall back to plain HTTP. Open the config Certbot just edited and add this inside the server block that listens on 443:

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

That’s HSTS. Once a browser sees it, it won’t even attempt an unencrypted connection to your domain for the next year. Only turn it on once HTTPS is working, because while it’s set there’s no graceful way back to HTTP.

9. Final verification

Test and reload Nginx one last time:

sudo nginx -t && sudo systemctl reload nginx

That’s it. Your site is live. Certbot sets up a background timer to renew your certificates automatically, so you don’t have to touch it again.