DEV Community

Cover image for Nginx from Zero: Config Files to HTTPS
GERALD IZUCHUKWU
GERALD IZUCHUKWU

Posted on

Nginx from Zero: Config Files to HTTPS

This would be a simple write-up explaining Nginx to a complete beginner.

Nginx Folder Structure

An Nginx folder can range from having just a single nginx.conf file to several other directories and files. Let's explore!
Nginx reads the nginx.conf file; while other files can be configured, Nginx readsnginx.conf directly; any other file must be explicitly included or referenced from there. A simple nginx.conf contains the following blocks of configuration

user www-data;
worker_processes auto;

events {
    worker_connections 1024;
}

http {
    include mime.types;
    default_type  application/octet-stream;

    sendfile on;
    keepalive_timeout 65;

    include /etc/nginx/conf.d/*.conf;
}
Enter fullscreen mode Exit fullscreen mode

user www-data tells Nginx what user the worker processes should run as. Nginx master process will always start as root, so it can bind to ports like 80 and 443 on the host machine, but as soon as the master process initializes, the worker processes run but not with root privileges. The user of the worker process can be obtained using the command ps aux | grep nginx, and for most Ubuntu or Debian Systems, the user is typically www-data

Why not "ubuntu"? The "ubuntu" user is your login account. It owns your shell, PM2 processes, and Node.js apps. Nginx should run under a dedicated service account like www-data because it has fewer privileges (better security), it's the standard on Ubuntu, and permissions are easier to manage.

worker_processes auto tells Nginx to create one worker per CPU core of the host machine. A specific number can be defined in this block, but it is safest for Nginx to adapt to the host machine. The worker processes perform the actual work of serving requests, while the master process manages them.

events{} block defines or configures how workers handle connections, and this is defined with worker_connections 1024, which is the maximum number of connections a worker can handle simultaneously. What this means is that if we have 4 workers and 1024 worker connections, then we will have roughly 4096(4*1024) simultaneous connections that the workers can handle.

http{} block is used to define everything related to HTTP and HTTPS configuration. Inside it, we find things like

  • include mime.types; This tells browsers what type of file they are receiving, e.g., text/html, text/css, etc.
  • default_type application/octet-stream; A fallback MIME type if Nginx encounters a file type it doesn't understand.
  • keepalive_timeout 65; Normally, after sending a response, the connection closes, but with this line, Nginx opens the connection for up to 65 seconds, waiting for additional requests before closing it. This reduces the overhead of creating new TCP connections for every resource on a page.
  • include /etc/nginx/conf.d/*.conf; By default, Nginx won't read other config files, but this tells Nginx to read every file with the .conf extension inside the /etc/nginx/conf.d/ folder as if the contents were written in the nginx.conf file. This file usually contains the server {} block

The typical server block contains the settings for one website or application. Here is a common example with the most important directives

server {
    # Port to listen on
    listen 80;

    # Domain names this server responds to
    server_name example.com www.example.com;

    # Root directory for static files
    root /var/www/html;

    # Default file to serve
    index index.html index.htm;

    # Access and error logs
    access_log /var/log/nginx/access.log;
    error_log  /var/log/nginx/error.log;

    # Handle requests
    location / {
        try_files $uri $uri/ =404;
    }

    # Custom error page
    error_page 404 /404.html;

    location = /404.html {
        internal;
    }
}
Enter fullscreen mode Exit fullscreen mode

listen 80; tells Nginx to accept incoming HTTP requests on port 80. For HTTPS, we will use listen 443 ssl;. Listening can be done on multiple ports.

server_name This specifies which hostnames this server block should respond to. It can be your domain name or, in most development cases, localhost.

server_name _; is used to match any hostname not defined in the server block. It is mostly used with a default server to catch all placeholder. You would use it when you have multiple server_name blocks and want explicit, deliberate control over what happens when none of them match, rather than letting Nginx silently pick the first one it finds.

root /var/www/html; This is the directory where Nginx looks for static files. If a request comes in for GET /about.html, Nginx looks for /var/www/html/about.html

index index.html index.htm; When a user visits www.example.com, Nginx looks for index.html or index.htm and serves whichever it finds first.

access_log and error_log are both directives (keywords) in Nginx, and it is used to specify where the access logs and errors will be saved. It is standard practice to save both logs in the same directory /var/log/nginx/. While access_logs are saved in access.log files, error_logs are saved in error.log files. Access logs record every request, and error logs record every error or warning request.

location {} block is used to specify how Nginx handles requests for a particular file path. It varies, depending on whether you are serving static files or reverse proxying. A simple static file location block might look like this

location / {
    try_files $uri $uri/ =404;
}
Enter fullscreen mode Exit fullscreen mode

This means that if someone requests/images/cat.png, Nginx checks and, if the file exists, serves it; if not, it returns a 404 error. The general syntax is to try the files one after the other until a match is found, and if no match is found, it resolves to the fallback, which is the last argument passed.

The location block can also look like this when it is reverse proxying

location / {
  proxy_pass http://localhost:3000;
  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;
}
Enter fullscreen mode Exit fullscreen mode
  • proxy_pass http://localhost:3000; Forwards the client's request to the backend application running on localhost (the same machine) on port 3000, then returns the backend's response to the client.
  • proxy_set_header Host $host; Sends the original Host header (for example, example.com) to the backend so it knows which domain the client requested, rather than seeing localhost:3000
  • proxy_set_header X-Real-IP $remote_addr; Passes the client's actual IP address to the backend application so it knows who made the request instead of only seeing the Nginx server's IP.
  • proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; Sends a list of all client and proxy IP addresses the request has passed through, appending the current client's IP to any existing list, which is useful when multiple reverse proxies are in place.

For example
If the client IP is 203.0.113.10
and the request passes through another proxy of 198.51.100.5
then Nginx forwards X-Forwarded-For: 203.0.113.10, 198.51.100.5

  • proxy_set_header X-Forwarded-Proto $scheme; Tells the backend whether the client originally connected using HTTP or HTTPS, allowing the application to generate correct URLs, perform HTTPS redirects, or enforce secure cookies.

For example
Client visits https://example.com → X-Forwarded-Proto: https
Client visits http://example.com → X-Forwarded-Proto: http

These proxy_set_header directives ensure your backend application sees information about the original client request, rather than only seeing requests coming from Nginx itself.

error_page 404 /404.html; This is used to customize an error page that Nginx will serve when we hit a 404 error, instead of the default 404.

That's it for the basic configuration on Nginx, and most of the time, this setup will be all you need to set up a simple HTTP static file server or reverse proxy server. Now all of these could be in the nginx.conf file, or you can create another file and call it whatever you like, say proxy_setup.conf, and because our server block most times is our largest block, we can move the server block to the file created and reference that file from our original nginx.conf. This keeps our files separated and easy to read.

HTTPS NGINX

What problem is HTTPS trying to solve?

Imagine you are logging in to a website via HTTP and entering your credentials. Since HTTP is just a hypertext transfer protocol, all the details you entered, including sensitive information, will be sent as plain text, and this information can and will be seen by anyone in between. This can include your ISP, someone on a public WiFi, and even a hacker. HTTP does not provide encryption, authentication, or integrity checks; this is where HTTPS comes in.

The S in HTTPS stands for secure, and this means information sent over HTTPS will be secured in the sense that it will be encrypted, will be authenticated, and will have some checks that look out for tampering. So HTTPS is HTTP running inside an encrypted TLS connection.

SSL vs TLS

SSL means Secure Sockets Layer. It was the original encryption protocol. SSL introduced the concept of a handshake before any data was exchanged. There were several versions of SSL, and version SSL 3.0 was the standard for a long time, but today SSL is obsolete, and TLS replaced it.

TLS means Transport Layer Security. Instead of fixing SSL indefinitely, the standards community developed a new protocol. Think of TLS as the next generation of SSL, and serves the same purpose. People still say 'SSL Certificate' till today, but that's simply because the SSL name stuck. In reality, if you obtain a certificate today from a certificate authority like Let's Encrypt or DigiCert, it's used with TLS, not SSL. So the terms SSL Certificate and TLS Certificate are commonly used interchangeably, but TLS Certificate is technically correct.

Compared with SSL, TLS offers:

  • Stronger encryption algorithms.
  • Better key exchange mechanisms.
  • Better protection against downgrade attacks.
  • Stronger integrity checks.
  • Faster handshakes (especially TLS 1.3).
  • Removal of outdated and insecure cryptographic features

What you need before setting up HTTPS

Setting up HTTPS is not as hard as most people think; you need a few things in place before you can set it up for reverse proxying on your server. First, you need to have a domain name pointing to your server's IP address. This is because HTTPS is secure and it uses a TLS/SSL certificate to prove the server's identity. Picture it this way: when you browse a website, for example, example.com, the browser asks, "Can you prove you are actually example.com?" and the server replies by providing a certificate. The browser verifies that the certificate was issued by a trusted Certificate Authority (CA), that the certificate hasn't expired, and that the hostname you're visiting matches one of the names on the certificate. Now, if you are visiting example.com and the certificate is for example.com, everything is fine. On the other hand, if you are visiting https://203.0.113.10 and the certificate is for example.com, the browser will show a certificate warning because the names don't match.

Let's Encrypt is the CA most people use for free certificates, and it requires a domain name, such as example.com, api.example.com, or myapp.dev. It checks/validates that you control the domain before issuing the certificate. It will not issue a normal certificate for https://203.0.113.10.

Can HTTPS work with an IP address? Yes, but it's uncommon. A Certificate Authority can issue a certificate containing an IP address instead of a domain, but not all CAs offer this; it's less common, and it's generally used in enterprise or internal environments.

Setting Up HTTPS NGINX

  • Get your domain name Some platforms allow you to own your own domain and subdomain names for free, such as FreeDomain. Let's say our domain name is myspeech.com
  • Point your domain name to your IP address After getting the domain name, you can use the same platform you got the name from to point your domain name to your IP address, or you can use a cloud provider like AWS Route53. This takes up to 3 minutes, and the command nslookup domain name allows you to confirm if your domain name points to your server IP address
  • Create the HTTP for your website This step is not compulsory, but it makes it easy to create the HTTPS version
  • Create a new config file in the sites-available folder sudo nano /etc/nginx/sites-available/myspeech.com.conf
  • Create the conf file for myspeech.com.conf
server {
    listen 80;
    listen [::]:80;
    server_name myspeech.com www.myspeech.com;

    location / {
      proxy_pass http://localhost:3002;
      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;
    }

    # Simple health check — no backend needed, nginx answers directly
    location /health {
        return 200 'OK\n';
        add_header Content-Type text/plain;
    }
}
Enter fullscreen mode Exit fullscreen mode
  • Enable the site sudo ln -s /etc/nginx/sites-available/myspeech.com.conf /etc/nginx/sites-enabled/ and disable the default nginx page sudo rm -f /etc/nginx/sites-enabled/default
  • Test config syntax sudo nginx -t and if "syntax is ok" then reload sudo systemctl reload nginx
  • Test your setup by hitting the health route curl http://myspeech.com/health or the service route curl http://myspeech.com/

If we get our desired response on HTTP, we can then move to HTTPS

  • Check if certbot exists certbot --version
  • Then run this single command sudo certbot --nginx -d myspeech.com.

Certbot will:

  • Detect your existing nginx config
  • Issue the cert from Let's Encrypt
  • Auto-modify your nginx config to add SSL
  • Set up the HTTP → HTTPS redirect automatically
  • Check the new configuration and reload nginx sudo nginx -t && sudo systemctl reload nginx
  • Test your setup by hitting the health route curl https://myspeech.com/health or the service route curl https://myspeech.com/

Why is HTTPS already working without you writing the 443 block?

That's certbot's magic. When you run certbot --nginx, it reads your existing config and automatically injects the SSL server block and the HTTP to HTTPS redirect into your conf file. Certbot gives you a working HTTPS setup, but it is bare minimum; you can choose to add other functionalities depending on your requirements

Top comments (0)