VPS DeploymentBeginner 12 min Lesson 14 of 30

Installing and Configuring Nginx

Install Nginx, set up a site, serve static files and get the default configuration into a sensible production state.

VPS Deployment · Lesson 14 of 30
0/30 done(0%)

What is it? #

Nginx is the piece that faces the internet on almost every VPS. It terminates TLS, serves static files and forwards everything else to your application.

Installing it takes one command. Getting it into a production-ready state takes a few more: removing the default site, creating your own server block, and setting a handful of defaults that are not right out of the box.

This lesson covers installation and a static site. The reverse proxy configuration for an application follows in the next lesson.

The habit to establish now is testing configuration before reloading.

Think of it like this #

Fitting the shopfront before the stock arrives. Door, signage, opening hours — all in place, so when the goods come in there is somewhere to put them.

Simple example #

A fresh server. You install Nginx, remove the default site, create a server block for your domain serving a static directory, and set global defaults for compression and limits.

Code #

BASH
sudo apt install nginx
sudo systemctl enable --now nginx
curl -I http://localhost           # the default page confirms it works

# Remove the default site — it should not serve your domain by accident
sudo rm /etc/nginx/sites-enabled/default
NGINX
# /etc/nginx/sites-available/example.com
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

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

    access_log /var/log/nginx/example.access.log;
    error_log  /var/log/nginx/example.error.log warn;

    location / {
        try_files <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>u</mi><mi>r</mi><mi>i</mi></mrow><annotation encoding="application/x-tex">uri</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.6595em;"></span><span class="mord mathnormal">u</span><span class="mord mathnormal" style="margin-right:0.0278em;">r</span><span class="mord mathnormal">i</span></span></span></span>uri/ =404;
    }

    location ~* \.(css|js|jpg|jpeg|png|gif|svg|woff2)$ {
        expires 30d;
        add_header Cache-Control "public";
        access_log off;
    }

    location ~ /\. { deny all; }        # no dotfiles
}
BASH
sudo mkdir -p /var/www/example.com/public
echo "<h1>It works</h1>" | sudo tee /var/www/example.com/public/index.html
sudo chown -R www-data:www-data /var/www/example.com

sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo nginx -t                       # always test first
sudo systemctl reload nginx
NGINX
# /etc/nginx/conf.d/defaults.conf — global settings worth setting
server_tokens off;                  # do not advertise the version number

client_max_body_size 20M;           # the 1M default rejects most uploads
client_body_timeout 15s;
send_timeout 15s;

gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript
           text/xml application/xml image/svg+xml;

add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
BASH
# A default server that rejects unknown host names
# Prevents your site being served for domains you do not own
sudo tee /etc/nginx/sites-available/default-deny > /dev/null <<'CONF'
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    return 444;                     # close the connection without responding
}
CONF
sudo ln -s /etc/nginx/sites-available/default-deny /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

How it works #

Nginx starts automatically after installation and serves a placeholder page. Removing the default site symlink prevents that page appearing for any domain that happens to point at your server.

Site configurations live in sites-available and are activated by a symlink in sites-enabled. Disabling a site means removing the symlink, with the configuration still available if you need it back.

try_files uriuriuri/ =404 looks for the file, then a directory index, then returns 404. It is the standard static-site pattern and prevents a request falling through to something unintended.

The asset location block sets long cache times for files that rarely change and turns off access logging for them, which removes a large share of log volume.

server_tokens off stops Nginx advertising its exact version in headers and error pages. It is a small thing, and version numbers are exactly what automated scanners look for.

client_max_body_size defaults to 1 MB, which rejects most uploads with a confusing 413 error. Setting it globally avoids rediscovering this per site.

The default server returning 444 closes the connection for requests with unknown host names. Without it, your site is served for any domain someone points at your IP address, which can affect search indexing and is occasionally abused.

Real-world use #

Nginx fronts almost every VPS deployment. The configuration above is the base that the next lessons build on with TLS and proxying.

Security headers are cheap to add and address several common browser-level issues. A full content security policy is more involved and is covered in the DevOps track.

Access logs feed the analysis techniques from the Linux logs lesson. Turning them off for static assets keeps the useful signal readable.

Hosting several sites is the same pattern repeated: one file per domain in sites-available, one symlink each.

The discipline of nginx -t before every reload is worth building now. It costs one second and catches the errors that would otherwise take the site down.

Common mistakes #

  • Leaving the default site enabled, so it responds for unexpected domains.
  • Keeping the 1 MB body size limit and rejecting legitimate uploads.
  • Reloading without testing the configuration first.
  • No default server, so the site is served for any domain pointed at the IP.
  • Forgetting to set ownership on the web root, producing permission errors.

Practice #

Install Nginx, remove the default site, and serve a static directory for your domain with long cache headers on assets. Add a default server that returns 444 for unknown hosts, set the global defaults, and confirm everything with nginx -t before reloading.

Quick quiz

  1. 1. Why remove the default site?

  2. 2. What does `try_files $uri $uri/ =404` do?

  3. 3. Why set client_max_body_size explicitly?

  4. 4. What does `server_tokens off` do?

  5. 5. What does returning 444 mean?

Summary

  • Install Nginx, remove the default site, and add a default server returning 444.
  • One configuration file per site, enabled by symlink.
  • Set global defaults for body size, compression and security headers.
  • Cache static assets hard and turn off their access logging.
  • Always run `nginx -t` before reloading.