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
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:
- Ask for your email (used for expiry warnings — provide a real one)
- Ask you to agree to the Let's Encrypt terms
- Verify domain ownership by serving a temporary file via your existing Nginx config
- Issue the certificate
- 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:
- HSTS header: tells browsers to never use plain HTTP for this domain again (only enable once you're sure HTTPS is working — it's a one-way commitment for the max-age duration)
- Other security headers: mitigate common attack classes
- gzip compression: shrinks text assets dramatically
- Static file caching: tells browsers to cache images/CSS/JS for 30 days
- HTTP/2: the
http2directive onlistenenables HTTP/2 for faster page loads
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.