Install and secure an Nginx server on Ubuntu
Install Nginx on Ubuntu and configure a site, UFW, HTTPS, permissions, headers, updates and checks while preserving remote access.
Install Nginx on Ubuntu and configure a site, UFW, HTTPS, permissions, headers, updates and checks while preserving remote access.
Requirements and DNS
Use an updated Ubuntu Server and a sudo account. For HTTPS, example.com must point to the public IP; publish www or AAAA records only if those endpoints work. Keep a second SSH session and recovery access. Replace example.com and check A/AAAA records with dig.
Installation and verification
Install the Ubuntu package; check service, syntax and local HTTP response. A local HTTP 200 does not establish public reachability.
sudo apt update
sudo apt upgrade
sudo apt install nginx
sudo systemctl enable --now nginx
sudo nginx -t
curl -I http://127.0.0.1
UFW firewall
Before enabling UFW allow the actual SSH port: OpenSSH in the example assumes the standard profile; use NUM/tcp for a custom port. Nginx Full opens TCP 80 and 443. After enabling UFW, test a new SSH session without closing the original. Also check provider firewall and IPv6.
sudo ufw status verbose
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw status verbose
Directory and permissions
The static example uses an admin-owned directory in group www-data, directories at 750 and files at 640. Confirm Nginx can traverse all parent directories. Dynamic applications need their own ownership rules; avoid indiscriminate recursive chown and mode 777.
sudo install -d -o "$USER" -g www-data -m 750 /var/www/example.com/public
printf '%s
' '<!doctype html><title>Example</title><h1>It works</h1>' | tee /var/www/example.com/public/index.html >/dev/null
chmod 640 /var/www/example.com/public/index.html
HTTP server block
Create /etc/nginx/sites-available/example.com using the example. Start with example.com only; add www if DNS and certificate cover it. The hidden-file location can affect /.well-known/: before Certbot, test its HTTP challenge and let the Nginx plugin arrange the exception, or define an explicit challenge location and test validation.
server {
listen 80;
listen [::]:80;
server_name example.com;
root /var/www/example.com/public;
index index.html;
access_log /var/log/nginx/example.com.access.log;
error_log /var/log/nginx/example.com.error.log;
location / { try_files $uri $uri/ =404; }
location ~ /\. { deny all; }
}
Enable and test
Create the symlink if absent, run nginx -t before reload and test the Host header locally. Disable the default site only after confirming it serves no needed content. Correct test failures before reloading.
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/example.com
sudo nginx -t && sudo systemctl reload nginx
curl -I -H 'Host: example.com' http://127.0.0.1
HTTPS certificate
Follow current Certbot Nginx instructions. Snap is supported; invoke /snap/bin/certbot explicitly instead of creating a link over an existing installation. Ensure example.com is publicly reachable on port 80; the example requests only that hostname. Certbot may edit the server block. Check redirects, certificate and renewal dry run.
sudo snap install --classic certbot
sudo /snap/bin/certbot --nginx -d example.com
sudo nginx -t && sudo systemctl reload nginx
curl -I https://example.com
sudo /snap/bin/certbot renew --dry-run
Headers and limits
In the http or HTTPS server context, server_tokens off hides the version number but is not a standalone security measure. Select headers for the application; location-level add_header settings can override inheritance. SAMEORIGIN can prevent legitimate embedding. The 8m body limit is an example. Enable HSTS only after thorough HTTPS and subdomain tests, without preload initially.
server_tokens off;
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;
client_max_body_size 8m;
Optional reverse proxy
Replace the static location / with this block; do not use two identical locations. Bind the backend to 127.0.0.1:8080 unless it must be public. Configure the app to trust forwarded headers only from the intended proxy; test redirects, WebSockets and timeouts where applicable.
location / {
proxy_pass http://127.0.0.1:8080;
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;
}
Maintenance and logs
Update the OS and application stack; assess unattended-upgrades for your maintenance policy. Inspect site logs, journal, disk space and nginx -t. A 403 can mean permissions or a missing index; a 502 can mean an unavailable upstream. Test before every reload.
sudo tail -f /var/log/nginx/example.com.error.log
sudo journalctl -u nginx --since '1 hour ago'
df -h
sudo nginx -t
Final checks
Verify SSH after UFW, DNS and IPv6, public ports, file permissions, nginx -t, HTTPS and renewal dry run. Fit uploads, headers and proxy to the application. Back up configuration and monitor services.
Official sources
Nginx: https://nginx.org/en/docs/ ; Certbot: https://certbot.eff.org/instructions?ws=nginx&os=snap ; UFW: https://documentation.ubuntu.com/server/how-to/security/firewalls/ .
Share
What's Your Reaction?
Like
0
Dislike
0
Love
0
Funny
0
Angry
0
Sad
0
Wow
0