Nginx is the web server that runs most of the internet. Pair it with Let's Encrypt for free, auto-renewing SSL certificates, and you have a setup that scales from a personal blog to a production SaaS. This guide walks through the install, the first server block, the SSL setup with certbot, and the few configuration tweaks that matter for production. Twenty minutes from a fresh VPS to a working HTTPS site.

📋

Prerequisites: Ubuntu 22.04 or 24.04 VPS, sudo access, and a domain name pointing to your VPS's IP. The DNS record (A record for IPv4, AAAA for IPv6) needs to resolve before we can issue an SSL certificate. Test with dig yourdomain.com or nslookup yourdomain.com.

Steps in this guide

  1. Install Nginx
  2. Open the firewall
  3. Create your first server block
  4. Install certbot for Let's Encrypt
  5. Issue the SSL certificate
  6. Verify auto-renewal
  7. Production-ready Nginx config
  8. Bonus: Nginx as a reverse proxy
  9. FAQ

Step 1: Install Nginx

Update package lists and install Nginx from Ubuntu's default repository (it's reasonably current):

sudo apt update
sudo apt install -y nginx

Nginx starts automatically. Verify:

sudo systemctl status nginx

Should say active (running). Visit http://your-vps-ip in your browser and you'll see the Nginx default page.

Step 2: Open the firewall

If you're running UFW (which you should be — see our Linux VPS hardening guide), allow HTTP and HTTPS:

sudo ufw allow 'Nginx Full'

The Nginx Full profile opens both port 80 (HTTP) and port 443 (HTTPS). Verify:

sudo ufw status

Step 3: Create your first server block

Server blocks (the Nginx equivalent of Apache's virtual hosts) let you serve different content for different domains. Create one for your domain.

Create the document root:

sudo mkdir -p /var/www/yourdomain.com
sudo chown -R $USER:$USER /var/www/yourdomain.com

Drop a placeholder index page:

cat > /var/www/yourdomain.com/index.html <<'EOF'
<!DOCTYPE html>
<html><head><title>yourdomain.com</title></head>
<body><h1>It works! 🐾</h1></body></html>
EOF

Create the Nginx config at /etc/nginx/sites-available/yourdomain.com:

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

Paste:

server {
    listen 80;
    listen [::]:80;
    server_name yourdomain.com www.yourdomain.com;

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

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

Enable the site by symlinking it into sites-enabled:

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

Remove the default site so it doesn't conflict:

sudo rm /etc/nginx/sites-enabled/default

Test the config and reload:

sudo nginx -t
sudo systemctl reload nginx

Visit http://yourdomain.com — you should see your placeholder page.

Step 4: Install certbot for Let's Encrypt

Certbot is the official Let's Encrypt client. Install via the Nginx-aware plugin:

sudo apt install -y certbot python3-certbot-nginx

Step 5: Issue the SSL certificate

One command:

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

Certbot will:

  1. Ask for your email (used for expiry warnings — provide a real one)
  2. Ask you to agree to the Let's Encrypt terms
  3. Verify domain ownership by serving a temporary file via your existing Nginx config
  4. Issue the certificate
  5. Modify your Nginx config to use the certificate and add an HTTP→HTTPS redirect

When it finishes, visit https://yourdomain.com. You should see the lock icon. Plain HTTP requests are now redirected to HTTPS.

⚠️

Rate limits: Let's Encrypt allows 50 certificates per registered domain per week and 5 duplicate certificate issuances per week. Use the --staging flag while you're testing — staging certs aren't trusted by browsers but don't count against rate limits.

Step 6: Verify auto-renewal

Let's Encrypt certificates expire after 90 days. Certbot installs a systemd timer that automatically renews them when they're within 30 days of expiry. Verify it's running:

sudo systemctl list-timers | grep certbot

You should see certbot.timer active. Test the renewal logic without actually renewing:

sudo certbot renew --dry-run

If that finishes without errors, your auto-renewal is working. You can stop thinking about SSL renewal forever.

Step 7: Production-ready Nginx config

The default config certbot generates is functional but can be improved. Here's a tightened version:

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name yourdomain.com www.yourdomain.com;

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

    # Certbot will manage these
    ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    # Security headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header X-Frame-Options "SAMEORIGIN" always;

    # Compression
    gzip on;
    gzip_types text/plain text/css text/xml application/json application/javascript;
    gzip_min_length 1024;

    # Caching for static files
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|webp|woff2)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

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

server {
    listen 80;
    listen [::]:80;
    server_name yourdomain.com www.yourdomain.com;
    return 301 https://$host$request_uri;
}

The key additions:

Test and reload:

sudo nginx -t && sudo systemctl reload nginx

Production-grade Nginx needs production hardware

Fast NVMe storage, real CPU cores, 20+ regions to put your server close to your users. Starting at $3.99/mo.

See VPS Plans →

Bonus: Nginx as a reverse proxy

The most common production use of Nginx isn't serving static files — it's reverse-proxying to a backend application (Node.js, Python, Rails, etc.). Here's the pattern:

server {
    listen 443 ssl http2;
    server_name app.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/app.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/app.yourdomain.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        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;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

The proxy_pass http://127.0.0.1:3000 sends requests to a Node.js or Python app listening on port 3000 of localhost. The X-Forwarded-* headers tell the backend the real client info (since from the backend's view, every request is coming from Nginx itself). The Upgrade and Connection headers enable WebSockets if your backend uses them.

This is the standard pattern for hosting Node.js, Python, Ruby, Go, and PHP-FPM apps in production. The backend listens on localhost; Nginx handles SSL, compression, caching, and routing.

FAQ

Should I use Nginx or Caddy?

Caddy auto-configures HTTPS by default and has simpler config syntax. It's a great pick for hobbyists and small projects. Nginx has more features, better performance under heavy load, and far more documentation. For production, Nginx; for quick personal projects, Caddy is fine.

Do I need to do anything for certificate renewal?

No. Certbot installs a systemd timer that runs twice daily and renews any cert within 30 days of expiry. As long as port 80 stays accessible to Let's Encrypt's servers, renewal is automatic. The only time you'll touch this is when you change DNS or add a new domain.

Can I use Let's Encrypt for wildcard certificates?

Yes, but you need DNS-01 challenge instead of HTTP-01. This requires your DNS provider to support API access (Cloudflare, Route53, and most major providers do). The certbot DNS plugins (certbot-dns-cloudflare, etc.) automate it.

Why is my site slow even with Nginx?

Nginx itself is rarely the bottleneck. Common culprits: slow backend application, slow database queries, missing gzip/caching, large unoptimized images, no CDN for global users. Run PageSpeed Insights against your site for specific guidance.

How do I host multiple sites on one VPS with Nginx?

Create a separate config file in /etc/nginx/sites-available/ for each domain, symlink each into sites-enabled/, run certbot --nginx -d for each. Each site has its own server block, its own root directory, and its own SSL certificate. A 1GB VPS easily hosts 5-10 small sites this way.

🐱
The OliveVPS Team

Nginx config archaeologists. We've debugged every variant of "502 Bad Gateway" known to humanity.