Installing Nginx is a good first step toward hosting a website, but a working web server is not automatically a secure one. A public-facing server needs attention to access controls, TLS, information disclosure, file permissions, and monitoring.

This guide covers practical Nginx hardening steps for Ubuntu Server 24.04 LTS. We'll make changes incrementally so you can test each one and recover safely if something goes wrong.

Prerequisites: You should have a working Nginx installation, an account with sudo privileges, and SSH access. Start with our Linux VPS setup guide and Nginx installation guide if you need those steps first.

1. Update Nginx and Ubuntu

Security hardening starts with keeping software patched. Configuration changes cannot compensate for every vulnerability in outdated software.

Update Ubuntu's package information and install available upgrades:

sudo apt update
sudo apt upgrade

Check the installed Nginx version and package candidate:

nginx -v
apt policy nginx

Ubuntu may backport security fixes without changing the upstream version number in the way you expect. Don't decide whether a package is vulnerable based on nginx -v alone; review Ubuntu's package and security notices too.

If updates require a reboot, plan one during an appropriate maintenance window. Keep your provider's recovery console available in case you encounter connection problems.

2. Back Up and Inspect the Current Configuration

Before changing configuration files, create a timestamped backup:

sudo cp -a /etc/nginx "/etc/nginx.backup-$(date +%F-%H%M%S)"

Keep the backup accessible only to trusted administrators. Then inspect the configuration Nginx loads:

sudo nginx -T

This command tests and prints the active configuration, including included files. Review it for unexpected server blocks, exposed paths, and settings you don't recognize. The output may reveal internal paths and operational details, so don't publish it without reviewing and redacting sensitive information.

3. Reduce Nginx Version Disclosure

Nginx can include its version in the Server response header and error pages. Disabling the version reduces unnecessary information disclosure, but does not replace security updates.

Create a small configuration file:

sudo nano /etc/nginx/conf.d/security.conf

Add:

server_tokens off;

On Ubuntu's packaged configuration, files in conf.d are included inside the main HTTP configuration context. Save the file, then test the configuration:

sudo nginx -t

If the test succeeds, reload Nginx:

sudo systemctl reload nginx

Check the response headers:

curl -sSI http://YOUR_DOMAIN

Replace YOUR_DOMAIN with your actual hostname. The Server header may still identify Nginx, but the version should no longer be included. Hiding a version number does not make vulnerable software safe; keep applying security updates.

4. Use HTTPS with Modern TLS

HTTPS encrypts traffic between visitors and your server and helps authenticate your website's identity. For a public website, obtain a valid TLS certificate from a trusted certificate authority, such as Let's Encrypt, and configure Nginx with the certificate and private key supplied by your certificate setup.

A typical HTTPS server block includes:

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    server_name example.com www.example.com;

    ssl_certificate /path/to/fullchain.pem;
    ssl_certificate_key /path/to/privkey.pem;

    ssl_protocols TLSv1.2 TLSv1.3;

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

Replace the example domain and certificate paths with values from your actual setup. These placeholder certificate paths will not work as written. Keep the private key readable only by the accounts and services that need it; Nginx's master process must be able to read it.

TLS 1.2 and TLS 1.3 are appropriate modern protocol versions for a typical current deployment. Ubuntu's Nginx package already uses modern TLS defaults; explicit ssl_protocols can document your intended policy.

After configuring HTTPS, test the configuration before reloading:

sudo nginx -t
sudo systemctl reload nginx

Then verify the HTTPS response:

curl -sSI https://example.com

Once HTTPS works, you can configure HTTP to redirect to HTTPS if that suits your site:

server {
    listen 80;
    listen [::]:80;

    server_name example.com www.example.com;

    return 301 https://$host$request_uri;
}

Use the redirect block only after HTTPS is working for the domain. If you use a CDN or reverse proxy, account for that architecture when configuring redirects.

5. Add Appropriate Security Headers

Security headers let a website communicate certain policies to browsers. They can reduce some client-side risks, but they don't fix application vulnerabilities.

For a straightforward website that doesn't need to be embedded in other sites, these headers may be a useful starting point inside the relevant HTTPS server block:

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;

X-Content-Type-Options tells browsers not to guess file types. Referrer-Policy controls how much referrer information is sent. X-Frame-Options limits framing by other sites; SAMEORIGIN allows the same origin to frame the page.

The always parameter adds these headers regardless of the response status. Nginx inherits add_header directives from a parent configuration level only when the current level has no add_header directives of its own. If a location block defines any headers, check that the headers you expect are still present there.

Avoid adding a generic Content Security Policy without checking the resources your site needs. An incorrect policy can block scripts, styles, fonts, analytics, or other legitimate content.

Add HSTS only after HTTPS is reliable

The Strict-Transport-Security header tells browsers to remember that a domain should use HTTPS. Add it only after HTTPS works reliably and you've confirmed the relevant hostnames support HTTPS. The includeSubDomains option can break subdomains that aren't ready for HTTPS, so don't enable it casually. Don't enable HSTS preloading without understanding its long-term effects.

6. Restrict Access to Website Files

Nginx should serve only files intended to be public. Application source code, private keys, environment files, backups, and secrets should not be stored inside a public web root.

A simple static site might use this layout:

/var/www/example.com/
└── html/
    ├── index.html
    ├── styles.css
    └── images/

Inspect ownership and permissions:

ls -ld /var/www/example.com
ls -la /var/www/example.com/html

Grant read access to public assets without making the entire site writable by everyone. Directories often use 755 and ordinary public files often use 644, provided the ownership and application design are appropriate.

Avoid commands such as chmod -R 777. They grant unnecessary write permissions and can increase the impact of a compromised account. For dynamic applications, keep secrets outside the web root and use permissions appropriate to the application user and deployment process.

7. Limit Exposed Services and Unnecessary Features

A web server should expose only the ports and services required for its role. Check UFW:

sudo ufw status verbose

A typical public website uses TCP ports 80 and 443 for HTTP and HTTPS. SSH must also remain reachable through the port you actually use. Remember that your cloud provider may have a separate firewall or security group; review both layers.

Review Nginx configuration for unnecessary directory listings, test endpoints, unused server blocks, and locations that expose internal files. If directory listings are enabled and not required, remove the autoindex on directive from the relevant location. Don't disable Nginx modules or remove configuration blindly; first establish which applications and features depend on them.

8. Configure Logging and Monitor Errors

Logs help investigate suspicious requests, misconfigurations, and service failures. Ubuntu's packaged Nginx installation commonly writes access and error logs to:

/var/log/nginx/access.log
/var/log/nginx/error.log

Review recent errors and requests:

sudo tail -n 50 /var/log/nginx/error.log
sudo tail -n 50 /var/log/nginx/access.log

Check the service status:

sudo systemctl status nginx

Look for repeated unusual requests, unexpected 4xx or 5xx responses, and configuration errors. A single failed request isn't necessarily an attack; investigate patterns and context before taking action.

Review log rotation and storage use so logs don't consume all available disk space. Avoid logging passwords, authorization tokens, or other sensitive values.

9. Validate the Final Configuration

After making changes, test the Nginx configuration:

sudo nginx -t

If the test succeeds, reload Nginx:

sudo systemctl reload nginx

Test both HTTP and HTTPS as applicable:

curl -sSI http://example.com
curl -sSI https://example.com

For a site that redirects HTTP to HTTPS, the first request should return a redirect response, while the HTTPS request should return a successful response when the site is available.

Also test the website in a browser and check that its images, stylesheets, scripts, and application routes still work. A successful configuration test confirms syntax and configuration loading; it does not guarantee that every security control is correct.

Conclusion

Hardening Nginx is a process, not a single configuration change. Keeping software updated, reducing unnecessary information disclosure, configuring HTTPS, applying suitable security headers, protecting website files, limiting exposed services, and reviewing logs all contribute to a stronger deployment.

These measures complement — rather than replace — secure SSH access, operating-system hardening, backups, monitoring, and application security.

Official References