techlogia — AI and Web Development Berlin
All courses
Free to read – no sign-up

nginx TLS Hardening

You harden nginx from an insecure default state (self-signed snakeoil cert + TLSv1 enabled + permissive ciphers + no security headers) into a modern TLS configuration. 7 tasks, about 60 minutes. Note: We use a self-signed certificate — browser warnings are expected here (in production you'd use Let's Encrypt, that's a separate module).

Duration: 60 minLevel: IntermediateExercises: 7

Harden nginx TLS configuration

Hardening nginx & TLS

TLS (Transport Layer Security, the successor to SSL) is the encryption behind HTTPS. It ensures nobody between browser and server can eavesdrop or alter data. A web server like nginx can, however, configure TLS insecurely — with outdated protocols, weak ciphers and missing protective headers.

Key terms

  • Certificate: proves the server's identity. Here you use a self-signed cert — browser warnings are expected (production uses Let's Encrypt).
  • TLS version: TLSv1.0/1.1 are broken; only TLSv1.2 and TLSv1.3 are safe.
  • Cipher suite: the concrete encryption algorithms. ECDHE+GCM suites are safe.
  • Security headers: HSTS enforces HTTPS, CSP limits what the browser may load (protection against XSS).

Your goal

You bring nginx from an insecure default to a modern TLS configuration. The config lives in /etc/nginx/sites-enabled/lab-default.conf; after changes verify with nginx -t and reload with systemctl reload nginx.

Exercises

  1. 1. Generate a self-signed certificate

    Concept: self-signed certificate. A certificate needs a private key (secret) and a public certificate (server.crt). Here you create both, self-signed. A helper script handles the complex openssl syntax and places the files in /etc/ssl/lab/ (valid 365 days).

    sudo /usr/local/bin/lab-gen-cert.sh

    Inspect the script first with bash -x /usr/local/bin/lab-gen-cert.sh to understand what openssl req -x509 does.

    Check: openssl x509 -in /etc/ssl/lab/server.crt -noout -subject shows a line with CN = ....

  2. 2. Point nginx at the new certificate

    Concept: effective configuration. A good certificate is useless if nginx keeps loading the old default path (snakeoil). Only the ssl_certificate directives make your cert effective.

    Set in /etc/nginx/sites-enabled/lab-default.conf (sudo nano ...):

    ssl_certificate     /etc/ssl/lab/server.crt;
    ssl_certificate_key /etc/ssl/lab/server.key;

    Then test and reload:

    sudo nginx -t && sudo systemctl reload nginx

    Check: the config contains ssl_certificate /etc/ssl/lab/server.crt.

  3. 3. Switch off legacy TLS versions

    Concept: TLS protocol versions. TLSv1.0 and TLSv1.1 are considered broken — modern browsers warn or refuse the connection. Allow only the current versions.

    Set in the nginx config:

    ssl_protocols TLSv1.2 TLSv1.3;

    Then sudo nginx -t && sudo systemctl reload nginx.

    Check: nginx -T shows ssl_protocols TLSv1.2 TLSv1.3.

  4. 4. Set a strong cipher suite

    Concept: cipher suite. The cipher suite defines the concrete encryption algorithms. Weak ones like RC4 or 3DES are exploitable. Safe are ECDHE suites with GCM (they provide forward secrecy).

    Set in the nginx config:

    ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers on;

    Then sudo nginx -t && sudo systemctl reload nginx.

    Check: the config contains an ssl_ciphers line with ECDHE+GCM (no RC4/3DES/MEDIUM).

  5. 5. Set the HSTS header

    Concept: HSTS. The Strict-Transport-Security header tells the browser: "for this site use HTTPS only, never HTTP again." This prevents downgrade attacks (e.g. SSL strip).

    Set in the server block of the config:

    add_header Strict-Transport-Security "max-age=15768000; includeSubDomains" always;

    Important: in the server block (not the location block) — otherwise inherited headers get overwritten.

    Then reload and check: curl -sIk https://localhost/ | grep -i strict.

    Check: curl -sIk https://localhost/ returns a Strict-Transport-Security: max-age=... header.

  6. 6. Set the Content-Security-Policy header

    Concept: CSP. The Content-Security-Policy limits which sources the browser may load content from. default-src 'self' means: only from your own domain. This is defence-in-depth against XSS — even injected JavaScript will not run.

    Set in the server block:

    add_header Content-Security-Policy "default-src 'self'" always;

    Then reload and check: curl -sIk https://localhost/ | grep -i content-security.

    Check: curl -sIk https://localhost/ returns Content-Security-Policy: default-src 'self'.

  7. 7. Resolve Lynis TLS suggestions

    Concept: Lynis audit. Lynis is a security audit tool that scans a system and provides a hardening score plus concrete suggestions. If your previous 6 steps are clean, the TLS-related Lynis hints (NETW-3032, HTTP-6624) should be covered.

    Run the audit and address any remaining TLS suggestions:

    sudo lynis audit system --quiet

    The suggestions are at the end of the output. If steps 1–6 are done, this check should pass automatically.

    Check: the Lynis hardening score has improved sufficiently over the baseline.

Now practice it yourself

Reading is good – doing is better. Start this course on a real Linux VM, right in your browser. A free account is all it takes.

Start for free

Lab content under CC BY 4.0 – free to use with attribution (© TechLogia).

nginx TLS Hardening