We’ll set up a plain Windows Server 2022 environment with NGINX and PowerShell to issue a Let’s Encrypt SSL certificate using the Posh-ACME PowerShell module. This guide will ensure that your environment is ready for certificate issuance and renewal using automated and Secure DNS validation and can be used to setup Windows 10\11 also.

Contents

Prerequisites

  1. Environment Setup:
    • Windows Server 2022 or Windows 10\11 with NGINX installed.
    • PowerShell installed (download from PowerShell).
    • OpenSSL installed (download from slproweb.com).
    • NSSM installed (download from nssm.cc).
    • GPG installed (download from gpg4win.org)
    • A domain you own, like <yourdomain.eu>
    • DNS access to manage your domain.
    • A static IP, SSL certificates are issued to domain names, not directly to IPs. However, your domain’s A record must point to a valid public IP at the time of certificate issuance and validation.
    • If your local development setup involves a public IP (e.g., through DDNS), you can set up a domain name pointing to your public IP and use that for certificate issuance, but we are not going to cover this here, to keep things as simple as possible.
    • Create the DNS A Record
      This step ensures your domain points to your server, which is a mandatory requirement for issuing an SSL certificate.
      0.1 Determine Your Public IP Address
      Open a browser and visit a service like WhatIsMyIP or IPChicken to find your server’s public IP address.
      Note down your public IP.
      0.2 Login to Your DNS Provider
      Access the DNS management console of your domain registrar or DNS provider.
      Locate the section for managing records.
      0.3 Create the DNS Records

      A Record for Root Domain (@)
      Host: Enter @ (represents the root domain, e.g., yourdomain.eu).
      Type: Select A (A Record).
      Value: Enter your public IP address.
      TTL: Set to the default value (e.g., 3600 seconds).
      AAAA Record for Root Domain (@)
      Host: Enter @ (represents the root domain, e.g., yourdomain.eu).
      Type: Select AAAA (AAAA Record).
      Value: Enter your public IPv6 address.
      TTL: Set to the default value (e.g., 3600 seconds).
      CNAME Record for www Subdomain
      Host: Enter www (represents the www subdomain, e.g., www.yourdomain.eu).
      Type: Select CNAME.
      Value: Enter yourdomain.eu.
      TTL: Set to the default value (e.g., 3600 seconds).

      Example DNS Configuration:
      Host | Type | Value | TTL
      @ | A | 203.x.1x3.2xx | 3600
    • @ | AAAA | <your-public-ipv6> | 3600
      www | CNAME | yourdomain.eu | 3600


      0.4 Verify the DNS Records
      Use a DNS lookup tool like DNSChecker or PowerShell’s nslookup command to verify the records:
    • nslookup yourdomain.eu
    • nslookup www.yourdomain.eu
    • nslookup -type=AAAA yourdomain.eu
    • nslookup -type=AAAA www.yourdomain.eu
      Ensure the responses match your public IP for yourdomain.eu and correctly point www.yourdomain.eu to yourdomain.eu.
      Important: DNS changes may take up to 48 hours to propagate, but they usually update within minutes.
  2. Posh-ACME Module:
  3. Valid Contact Email Address:
    • An email like <email>@yourdomain.eu that will be associated with the Let’s Encrypt ACME account for certificate expiration notifications.
  4. Folder Setup
    • Before starting, you should have the following folder structure on your C:\ drive for ease of organization:
    • Apps: To store tools like nssm (Non-Sucking Service Manager).
    • Nginx: The folder where Nginx will be extracted and configured.
    • Certs: To store SSL certificates generated by Let’s Encrypt.

Step 1: Install PowerShell

To top

To ensure compatibility and access to the latest features, it’s important to install and verify the correct version of PowerShell. Follow these steps:

Install the Latest Version of PowerShell:

Download and install the latest PowerShell.

Download the installer suitable for your operating system (e.g., Windows, macOS, Linux).

Run the installer and follow the on-screen instructions to complete the installation.

Verify the Installed PowerShell Version:

Open PowerShell with administrative privileges, make sure that you opened the latest version 7.xx and higher that you just installed, not the one that comes installed with Windows.

Execute the following command to check the installed PowerShell version:

pwsh -v 

This command will display the version number of PowerShell. Ensure it is version 7.0 or higher for optimal compatibility.

Set the Execution Policy to Allow Script Execution:

PowerShell’s execution policy determines which scripts are permitted to run on your system. To allow the execution of local scripts while maintaining security, set the execution policy to RemoteSigned for the current user:

Open PowerShell with administrative privileges, make sure that you opened the latest version 7.xx and higher that you just installed, not the one that comes installed with Windows.

Run the following command:

Set-ExecutionPolicy RemoteSigned -Scope CurrentUser

When prompted, confirm the change by typing Y and pressing Enter.

Setting the execution policy to RemoteSigned allows scripts created locally to run without a digital signature, while scripts downloaded from the internet require a trusted signature. This balance enhances security without hindering local script development.

Note: Adjusting the execution policy affects script execution behavior. For more details on execution policies and their implications, refer to the official Microsoft documentation: about_Execution_Policies


Step 2: Install Nginx on Windows

To ensure your environment is ready to host your website with Nginx, follow these steps to install and configure Nginx on Windows Server 2022 or Windows 10/11.

To top

2.1 Download Nginx

  1. Visit the Official Nginx Download Page:
  2. Choose the Stable Version for Windows:
    • Under the “Stable version” section, find the link for the Windows binaries (e.g., nginx-1.24.0.zip).
    • Click on the .zip file to download it.

2.2 Extract Nginx

  1. Create a Directory for Nginx:
    • Open File Explorer and create a new folder where you’d like to install Nginx, e.g., C:\nginx.
  2. Extract the Zip File:
    • Right-click on the downloaded nginx-1.xx.x.zip file.
    • Select Extract All.
    • Choose C:\nginx as the destination folder.
    • Ensure the extraction results in the nginx.exe file and folders like conf, html, and logs inside C:\nginx.

2.3 Start Nginx

  1. Open PowerShell as Administrator:
    • Press Win + X and select Windows PowerShell (Admin).
  2. Navigate to the Nginx Directory: cd C:\nginx
  3. Start the Nginx Server: .\nginx.exe
  4. The cursor in this PowerShell will start blinking indicating that Nginx is running, it will not accept any other commands, closing the window will NOT stop the server from running
  5. Verify Nginx is Running:
    • Open a web browser and navigate to http://localhost.
    • You should see the default Nginx welcome page.

2.4 Configuring Nginx Firewall Rules on Windows Server 2022

2.4.1: Access Windows Defender Firewall

  1. Open Windows Defender Firewall with Advanced Security:
    • Press Win + R, type wf.msc, and hit Enter.
  2. Select Inbound Rules from the left-hand panel.
  3. Repeat the steps below for Outbound Rules after completing inbound configuration.

2.4.2: Create Inbound Rules

Rule for HTTP (Port 80)

  1. Click New Rule in the right-hand menu.
  2. Select Port and click Next.
  3. Choose TCP and specify Specific Local Ports as 80.
  4. Click Next and select “Allow the Connection” (no “if it is secure” option exists for inbound rules).
  5. Select all profiles: Domain, Private, and Public.
  6. Name the rule (e.g., Nginx HTTP Inbound) and click Finish.

Rule for HTTPS (Port 443)

  1. Click New Rule in the right-hand menu.
  2. Select Port and click Next.
  3. Choose TCP and specify Specific Local Ports as 443.
  4. Click Next and select “Allow the Connection”.
  5. Select all profiles: Domain, Private, and Public.
  6. Name the rule (e.g., Nginx HTTPS Inbound) and click Finish.

Rule for Nginx Executable

  1. Click New Rule in the right-hand menu.
  2. Select Program and click Next.
  3. Choose This Program Path and browse to the Nginx executable location:
    • For example: C:\nginx\nginx.exe.
  4. Click Next and select “Allow the Connection”.
  5. Select all profiles: Domain, Private, and Public.
  6. Name the rule (e.g., Nginx Executable Inbound) and click Finish.

2.4.3: Create Outbound Rules

Rule for HTTP (Port 80)

  1. Click New Rule in the Outbound Rules section.
  2. Select Port and click Next.
  3. Choose TCP and specify Specific Local Ports as 80.
  4. Click Next and select “Allow the Connection”.
  5. Select all profiles: Domain, Private, and Public.
  6. Name the rule (e.g., Nginx HTTP Outbound) and click Finish.

Rule for HTTPS (Port 443)

  1. Click New Rule in the Outbound Rules section.
  2. Select Port and click Next.
  3. Choose TCP and specify Specific Local Ports as 443.
  4. Click Next and select “Allow the connection if it is secure”.
    • This ensures only secure (encrypted) outbound connections.
  5. Select all profiles: Domain, Private, and Public.
  6. Name the rule (e.g., Nginx HTTPS Outbound) and click Finish.

Create Firewall Rules for IPv6 Using PowerShell

Add rules for HTTP (port 80), HTTPS (port 443), and ICMPv6 for necessary IPv6 functionality.

Inbound Rules for IPv6:

New-NetFirewallRule -DisplayName "Nginx HTTP IPv6 Inbound" -Direction Inbound -Protocol TCP -LocalPort 80 -RemoteAddress Any -Action Allow -Profile Any
New-NetFirewallRule -DisplayName "Nginx HTTPS IPv6 Inbound" -Direction Inbound -Protocol TCP -LocalPort 443 -RemoteAddress Any -Action Allow -Profile Any
New-NetFirewallRule -DisplayName "ICMPv6 Inbound" -Direction Inbound -Protocol ICMPv6 -IcmpTypeCode 128:0,129:0 -RemoteAddress Any -Action Allow -Profile Any

Outbound Rules for IPv6:

New-NetFirewallRule -DisplayName "Nginx HTTP IPv6 Outbound" -Direction Outbound -Protocol TCP -LocalPort 80 -RemoteAddress Any -Action Allow -Profile Any 
New-NetFirewallRule -DisplayName "Nginx HTTPS IPv6 Outbound" -Direction Outbound -Protocol TCP -LocalPort 443 -RemoteAddress Any -Action Allow -Profile Any
New-NetFirewallRule -DisplayName "ICMPv6 Outbound" -Direction Outbound -Protocol ICMPv6 -IcmpTypeCode 128:0,129:0 -RemoteAddress Any -Action Allow -Profile Any

What is ICMP?

ICMP stands for Internet Control Message Protocol, a critical part of the IP protocol suite used to send diagnostic and error messages between devices in a network. It is not used to transmit application data but rather to ensure that devices can communicate effectively.

  • ICMPv4 is the version used for IPv4 networks.
  • ICMPv6 is the updated version designed specifically for IPv6 networks.

Why is ICMPv6 Important?

ICMPv6 is essential for IPv6 functionality and introduces additional capabilities that make it a cornerstone of IPv6 communication. Here are some of the reasons it is vital:

  1. Neighbor Discovery Protocol (NDP): ICMPv6 supports NDP, which replaces ARP (Address Resolution Protocol) in IPv6. It helps devices:
    • Discover other devices on the same network.
    • Resolve IPv6 addresses to MAC addresses.
    • Determine the best route for packets.
  2. Path MTU Discovery: ICMPv6 enables devices to determine the maximum transmission unit (MTU) for a path, preventing fragmentation and ensuring efficient data transfer.
  3. Echo Request and Reply (Ping):
    • ICMPv6 types 128 (Echo Request) and 129 (Echo Reply) are used for ping commands to test connectivity between devices.
    • These are critical for diagnosing and troubleshooting IPv6 networks.
  4. Error Reporting: ICMPv6 reports errors in packet processing, such as:
    • Destination unreachable.
    • Packet too big.
    • Time exceeded.
  5. Router Discovery: ICMPv6 facilitates router advertisements and solicitations, allowing devices to find and configure default gateways dynamically.

Why Allow ICMPv6 in Firewall Rules?

ICMPv6 plays a much larger role in IPv6 compared to ICMP in IPv4. Blocking ICMPv6 indiscriminately can break essential IPv6 features like NDP, path MTU discovery, and even basic connectivity diagnostics. Therefore, enabling specific ICMPv6 types ensures that your network operates efficiently and securely.

Key ICMPv6 Types and Their Purposes

TypeCodeNamePurpose
1280Echo RequestUsed by the ping command to test IPv6 connectivity.
1290Echo ReplyReply to an Echo Request, confirming that the target is reachable.
1330Router SolicitationRequests routers to send router advertisements for auto-configuration.
1340Router AdvertisementUsed by routers to advertise their presence and configuration to devices.
1350Neighbor SolicitationSimilar to ARP in IPv4, used to discover neighbors and resolve MAC addresses.
1360Neighbor AdvertisementUsed to respond to neighbor solicitations and to inform of changes in link-layer addresses.
1VariousDestination UnreachableInforms the sender that a destination is unreachable for some reason (e.g., no route to host).

Minimal ICMPv6 Types for Proper Functionality

  • Echo Request (128:0) and Echo Reply (129:0): For testing connectivity with ping.
  • Neighbor Solicitation (135:0) and Neighbor Advertisement (136:0): For local address resolution and communication.
  • Router Solicitation (133:0) and Router Advertisement (134:0): For dynamic IPv6 network configuration.

Verify the Rules:

<code>Get-NetFirewallRule | Where-Object { $_.DisplayName -like "Nginx*IPv6*" }</code>

2.4.4: Test the Configuration

  1. Restart the Nginx service to apply changes:
    • Open Powershell as Administrator and run: cd C:\nginx, .\nginx.exe -s reload
  2. Test HTTP :
    • Open a browser and navigate to:
      • http://your-server-ip (for HTTP).
  3. Check the Windows Firewall Logs:
    • Enable logging in Windows Firewall (via Properties > Logging) to verify that traffic is allowed for Nginx.

2.5 Configure Nginx for Your Domain

Open the Nginx Configuration File:

Navigate to C:\nginx\conf\.

Open nginx.conf in a text editor (e.g., Notepad++ or Visual Studio Code), by default Nginx for Windows comes with this configuration:

worker_processes  1;

events {
    worker_connections  1024;
}

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

    sendfile        on;

    keepalive_timeout  65;

    # HTTP Server Block (Port 80)
    server {
        listen       80;       
        #the http server only serves at localhost
        server_name  localhost;

        location / {
            root   html;
            index  index.html index.htm;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

Modify the Server Block:

Locate the existing server block that listens on port 80.

Edit server_name and replace it with the following configuration:

 # HTTP Server Block (Port 80)
 server {
         listen       80;
          #the http server now serves your domain
          
          # ===This is the current addition 
          listen [::]:80; #IPv6
          server_name yourdomain.eu www.yourdomain.eu;
          # ===Here ends the current addition 
        
    }

Replace yourdomain.eu with your actual domain name.

Ensure the root directive points to the correct path where your website files will be stored.

Save the Configuration File:

After making the changes, save nginx.conf.


2.6 Add Your Website Files

  1. Place Your Website Content:
    • Copy your website files (e.g., index.html, images, stylesheets) into the C:\nginx\html directory.
  2. Test the Content Locally:
    • Ensure that index.html is present in the html directory.

2.7 Restart Nginx to Apply Changes

  1. Reload Nginx Configuration:
    • In the PowerShell, run: cd C:\nginx, .\nginx.exe -s reload
    • This command tells Nginx to reload the configuration without stopping the server.

2.8 Test Your Site

  1. Verify the Site Locally:
    • Open a web browser and navigate to http://localhost or http://127.0.0.1.
    • You should see your website’s homepage instead of the Nginx welcome page.
  2. Test with Your Domain Name:
    • If your domain’s DNS records point to your server’s IP address, you can test by navigating to <http://yourdomain.eu>.
    • Note: DNS propagation may take some time if you recently updated your DNS records.
  3. Optional: Update Hosts File for Local Testing:
    • If DNS is not yet set up, you can edit your local hosts file to map your domain to localhost:
      • Open the hosts file located at C:\Windows\System32\drivers\etc\hosts in a text editor with administrative privileges.
      • Add the following line at the end of the file: 127.0.0.1 yourdomain.eu www.yourdomain.eu
      • Save the file.
      • Now, navigating to <http://yourdomain.eu> in your browser will load your local Nginx server.

Step 3: Download and Install NSSM, run Nginx as a service

To top

Stop Nginx

Before we install NSSM we have to stop Nginx as it is already running at a Powershell window from step 2.3, open another PowerShell window as administrator and navigate to Nginx folder:

cd C:\nginx

then stop it:

.\nginx -s stop

The running Nginx cursor at the original PowerShell window will also exit to PS C:\Nginx>_

Check if the Nginx process is running:

Get-Process -Name nginx

Test Nginx Configuration: Navigate to the Nginx directory and validate its configuration:

cd C:\nginx
.\nginx.exe -t

You should see the message:


nginx: the configuration file C:\Nginx/conf/nginx.conf syntax is ok
nginx: configuration file C:\Nginx/conf/nginx.conf test is successful

Use this command to troubleshoot the installation of Nginx along with:

PS C:\Nginx> tasklist /fi "imagename eq nginx.exe"

Image Name                     PID Session Name        Session#    Mem Usage
========================= ======== ================ =========== ============
nginx.exe                     5008 Services                   0      7,644 K
nginx.exe                     1252 Services                   0      7,896 K

With NSSM managing Nginx, tasklist becomes a reliable tool to verify:

  1. That Nginx Processes are Running:
    • Check if the master and worker processes are active.
  2. Troubleshooting Conflicts:
    • If Stop-Service nginx doesn’t fully stop the server (common when Nginx was started manually or NSSM conflicts), tasklist helps confirm if processes are still running.

Note:

After installing NSSM and running Nginx as a service, .\nginx -s stop and .\nginx.exe -s reload will no longer work, you will have to use Stop-Service -Name Nginx and Restart-Service nginx


Install NSSM

  1. Download NSSM from the official website: https://nssm.cc/download.
    • Choose the version that matches your system (e.g., nssm-2.24.zip).
  2. Extract the Zip File:
    • Extract it to a folder, such as C:\Apps\NSSM.

Add NSSM to the System PATH

You can add C:\Apps\NSSM to the PATH using the following PowerShell command to make nssm accessible from any command line

[System.Environment]::SetEnvironmentVariable("Path", $Env:Path + ";C:\Apps\nssm-<YOURVERSION>\win64", [System.EnvironmentVariableTarget]::Machine)

Use NSSM to Install Nginx as a Service

Once nssm is in your PATH or you’ve navigated to its directory, you can install Nginx as a service with the following command

nssm install Nginx "C:\Nginx\nginx.exe"

Start the Nginx Service

Once installed, you can start the Nginx service using

nssm start Nginx

Or, manage it using standard PowerShell commands:

Stop Nginx Service:

Stop-Service nginx

Start Nginx Service:

Start-Service nginx

Restart Nginx Service:

Restart-Service nginx

Check the status of Nginx if Nginx is installed as a Windows service.

Get-Service -Name nginx

Explanation:

  • Get-Service: Retrieves the status of Windows services.
  • -Name nginx: Specifies the service name (assuming Nginx was installed and registered as a service with the name nginx).

Possible Outputs:

  • Running: Nginx is actively running.
  • Stopped: Nginx is installed but not running.
  • Error: The service does not exist or is not properly configured.

Commands to Avoid:

Try to start Nginx with: cd C:\nginx
.\nginx.exe


Try to stop Nginx with: .\nginx -s stop.

Try to quit Nginx gracefully with: .\nginx -s quit.

To to reload the Nginx configuration file with: .\nginx -s reload.

To to reopen the Nginx log files with: .\nginx -s reopen.

Why?: These commands run Nginx independently, bypassing the service manager (NSSM).

  • The Problem: If you later try to stop the service using Stop-Service nginx, the service stops, but the manually started Nginx instance continues running. This causes inconsistencies and potential port conflicts.
  • Also, stop, quit, reload and reopen will raise errors as now Nginx is managed by NSSM.
  • .\nginx -s stop
  • nginx: [error] OpenEvent(“Global\ngx_stop_5092”) failed (5: Access is denied)

Key Recommendations

  1. Use .\nginx.exe -t for testing configuration files—it’s the only “native” command you can safely run alongside NSSM.
  2. Always use Stop-Service, Start-Service, or Restart-Service for controlling the server.
  3. Never start Nginx manually with .\nginx.exe after installing it as a service, this will result into starting a “phantom” Session 1 that you will not be able to stop with Stop-Service nginx or the Services Windows control.
tasklist /fi "imagename eq nginx.exe"

Image Name                     PID Session Name        Session#    Mem Usage
========================= ======== ================ =========== ============
nginx.exe                     3476 Console                    1      7,648 K
nginx.exe                     5548 Console                    1      7,936 K
nginx.exe                     5008 Services                   0      7,644 K
nginx.exe                     1252 Services                   0      7,896 K

Using nssm edit Nginx: Key Settings and Examples

Now, Nginx should run as a Windows service, starting automatically if configured to do so.

The command nssm edit Nginx opens a configuration interface provided by NSSM (Non-Sucking Service Manager), allowing you to adjust various settings for the Nginx service. Here’s what you can configure through this interface and why it might be useful:

Application Tab

  1. Path:
    • Ensure this points to the Nginx executable: C:\nginx\nginx.exe
  2. Startup Directory:
    • This should be the Nginx root directory: C:\nginx
  3. Arguments (optional):
    • Specify a custom configuration file if needed: -c C:\nginx\conf\nginx.conf
    • Leave blank if using the default configuration.

Details Tab

  1. Display Name:
    • Change the service name for clarity, e.g.: Nginx Web Server
  2. Description:
    • Add a brief description of the service, e.g.: Runs Nginx as a web server on this machine.
  3. Startup Type
  4. Choose one of the following:
    • Automatic: To start the service when Windows boots.
    • Automatic(Delayed Start): To start the service after another required process starts.
    • Manual: To start the service manually when needed.
    • Disabled: To prevent the service from starting at all.
  5. This ensures your service starts according to your requirements.

Logon Tab

  1. Run As:
    • By default, Nginx runs under the Local System account.
    • If security is critical, specify a limited user account with appropriate permissions for running Nginx.
  2. Username & Password:
    • Enter the credentials if you’re running the service under a specific user account.
    Example: Username: myServerUser Password: mySecurePassword123

I/O Tab

  1. Log Redirection:
    • Redirect Nginx output logs for debugging or monitoring:
      • Standard Output (stdout): C:\nginx\logs\stdout.log
      • Standard Error (stderr): C:\nginx\logs\stderr.log

File Rotation Tab

The File Rotation Tab in NSSM lets you manage the size and lifespan of the logs generated by Nginx. This helps prevent logs from consuming excessive disk space over time. Here’s how it works:

Key Settings:

  1. Replace Existing Output and/or Error Files:
    • When enabled, the current logs (stdout.log, stderr.log) will be replaced each time the service starts.
    • This is useful for debugging but risks losing historical logs.
  2. Rotate Files:
    • Enables periodic rotation of log files without requiring a service restart.
    • Useful for managing continuous logs.
  3. Restrict Rotation to Files Older Than:
    • Specify a time limit (e.g., logs older than 7 days).
    • Only logs that meet this age will be rotated.
  4. Restrict Rotation to Files Bigger Than:
    • Rotate logs once they exceed a specified size (e.g., 100 MB).

Which Files to Rotate:

  • The stdout.log and stderr.log generated by NSSM.
  • Use Nginx’s access.log and error.log for HTTP-specific logs. While these are not managed by NSSM, they can be rotated manually or via scripts (see below).

Combining File Rotation with PowerShell Automation

On Windows, NSSM’s rotation lacks advanced features like compression or automatic deletion. You can use a PowerShell script to handle log rotation effectively. For example:

# Rotate and compress Nginx logs
$logs = Get-ChildItem -Path "C:\nginx\logs" -Filter "*.log"
foreach ($log in $logs) {
    Compress-Archive -Path $log.FullName -DestinationPath "$($log.FullName).zip"
    Remove-Item $log.FullName
}

Steps to Automate:

  1. Save this script as rotate_logs.ps1.
  2. Schedule it in Task Scheduler to run daily or weekly.
  3. Alternatively, configure NSSM to run this script as a service for continuous monitoring.

Dependencies Tab

  • Purpose: Specifies which system services must start before this service can begin.
  • Example Use: If Nginx depends on a database service or other applications, you can list them here to ensure proper startup order.
  • How to Use: Add the name of the dependent services. For instance:
    • SQLSERVER (if SQL Server must start before Nginx).
    • W3SVC (if a specific web service must start first).

Process Tab

  • Priority: Sets the CPU priority for the service:
    • Normal: Default priority for most applications.
    • Above Normal/High: For critical services needing more CPU resources.
  • Affinity: Select specific CPUs or cores to assign to the process.
    • Example: If your server has 8 cores, limiting Nginx to 2 cores might reduce conflicts with other processes.
  • Console Window: Enables or disables a console window when the service runs. Useful for debugging during testing but unnecessary in production.

Shutdown Tab

  • Generate Control-C: Sends a signal to gracefully stop the application.
  • Send WM_CLOSE to Windows: Attempts to close the application via Windows messaging.
  • Post WM_QUIT to Threads: Sends a quit message to threads, ensuring orderly shutdown.
  • Terminate Process: A hard stop if all other options fail.
  • Timeouts: Define how long (in milliseconds) to wait before escalating shutdown actions.

Exit Actions Tab

  • Throttling: Prevents rapid restarts if the service crashes multiple times in quick succession. For example:
    • Delay restart if the service runs for less than 1500ms (to avoid unnecessary retries).
  • Restart Options:
    • Restart Application: Automatically restarts Nginx after a crash.
    • Delay Restart By: Adds a pause before restarting (useful to avoid resource exhaustion).
    • No Action: Takes no action on unexpected exits.

Environment Tab

  • Environment Variables: Define custom variables for the service.
    • Example: NGINX_ENV=production.
  • Replace Default Environment: Replaces the inherited environment variables with the specified ones.

Complete Example Configuration for nssm edit Nginx

Here’s a step-by-step guide for setting up and customizing the Nginx service using NSSM:

  1. Application Tab:
    • Path: C:\nginx\nginx.exe
    • Startup Directory: C:\nginx
    • Arguments: -c C:\nginx\conf\nginx.conf (optional)
  2. Details Tab:
    • Display Name: Nginx Web Server
    • Description: Runs Nginx as a web server for this machine.
  3. Logon Tab:
    • Default: Local System Account
    • If security is a concern, use a specific user account with limited permissions.
  4. I/O Tab:
    • stdout: C:\nginx\logs\stdout.log
    • stderr: C:\nginx\logs\stderr.log
    • Select “Append” to preserve existing logs or “Overwrite” to start fresh with each restart.
  5. Shutdown Tab:
    • Enable graceful shutdown options:
      • Generate Control-C
      • Send WM_CLOSE to Windows
      • Post WM_QUIT to threads
  6. Exit Actions Tab:
    • Restart Action: Restart application
    • Delay Restart: 5000 ms (5 seconds)
  7. File Rotation Tab:
    • Enable rotation for stdout.log and stderr.log with size and/or age restrictions.
    • Use a PowerShell script (example above) for advanced rotation of Nginx logs.

Testing and Debugging

Edit the Service:

nssm edit Nginx

Apply Changes:

Stop-Service -Name Nginx
Start-Service -Name Nginx

Verify Logs:

Check stdout.log and stderr.log in C:\nginx\logs for service activity.

Monitor access.log and error.log for HTTP-specific events.


Additional Resources

For more detailed documentation and advanced NSSM usage, visit: https://nssm.cc/usage

For the official documentation for Nginx on Windows, vist: https://nginx.org/en/docs/windows.html


Configure Nginx to Run at Startup (Optional)

  1. Create a Scheduled Task:
    • Since Nginx does not natively run as a Windows service, you can create a scheduled task to start it on system boot if you do not wish to install NSSM (Non-Sucking Service Manager)

Step 4: Install and Configure Posh-ACME

To top

Open PowerShell as an administrator and install the module:

Install-Module -Name Posh-ACME -Scope CurrentUser -Force

Verify installation:

Get-Module -ListAvailable Posh-ACME

Step 5: Configure Let’s Encrypt Staging Server

To top

To avoid rate limits during testing, use the Staging Server of Let’s Encrypt, this way you will know that everything works well before you ask to issue the Real production SSL, you will change to production at Step 7:

Set-PAServer LE_STAGE

Step 6: Generate a Staging(test) Certificate

To top

Create an ACME account and accept the Terms of Service:

New-PACertificate -Domain <yourdomain.eu>,www.<yourdomain.eu> -AcceptTOS -Contact <email@yourdomain.eu>

Follow the instructions to create a DNS TXT record for domain validation.

PS C:\Windows\System32> New-PACertificate -Domain <yourdomain.eu>,www.<yourdomain.eu> -AcceptTOS -Contact <email@yourdomain.eu>

Please create the following TXT records: ------------------------------------------ 

_acme-challenge.<yourdomain.eu> -> cK9yLugh19_kk2u-DGkb4Wvh6sRql1qq7lrjNwAxxxxx
_acme-challenge.www.<yourdomain.eu> -> iGXBQsTpxKxUZVXIx6dEVDoOKyu5gR_JHDwg4xxxx

The message indicates that the ACME protocol used by Let’s Encrypt requires you to prove ownership of the domain yourdomain.eu by creating a DNS TXT records with specific names and values.

Instructions Provided

  1. TXT Record Name:
    _acme-challenge.<yourdomain.eu>
    This is the name of the TXT record you need to add to your DNS configuration.
  2. TXT Record Value:
    cK9yLugh19_kk2u-DGkb4Wvh6sRql1qq7lrjNwAxxxxx
  3. TXT Record Name for www:
    _acme-challenge.www.<yourdomain.eu>
    This is the name of the TXT record you need to add to your DNS configuration.
  4. TXT Record Value:
    iGXBQsTpxKxUZVXIx6dEVDoOKyu5gR_JHDwg4xxxx
    The values of the TXT records, will be different each time you issue an SSL.

Why It’s Needed

To top

This is part of the dns-01 challenge for domain validation:

  • Let’s Encrypt asks you to create a TXT record in your domain’s DNS to prove that you own or control the domain.
  • The ACME server (e.g., Let’s Encrypt) will query the DNS system to verify that the records exist and matches the expected value.

What to Do

  1. Log in to Your DNS Management Console:
    Access the DNS settings for the yourdomain.eu domain, typically provided by your domain registrar or hosting provider.
  2. Add a TXT Record:
    • Name: _acme-challenge
      (Some DNS providers automatically append .yourdomain.eu to the name, so just _acme-challenge may suffice.)
    • Type: TXT
    • Value: Q11tHfO0D8J34mD74ctTMZKUEDZpZ5svHXB_ONFtxxx
  3. Wait for DNS Propagation:
    Allow time for the TXT record to propagate across the DNS system. This can take a few minutes to several hours, depending on your DNS provider.
  4. Verify the TXT Record :
    • It’s important to highlight that DNS changes can take time to propagate. You should verify that the TXT record is publicly accessible before proceeding.
      • Verify the TXT records with a DNS query tool, like nslookup that you will check in another PowerShell terminal, DO NOT INTERRUPT THE PROCESS AT THE ORIGINAL TERMINAL:
      • nslookup -q=txt _acme-challenge.<yourdomain.eu>
      • nslookup -q=txt _acme-challenge.www.<yourdomain.eu>
      • If the values that you entered at your TXT RECORD is returned, you can proceed with the certificate generation.
  5. Continue the Certificate Request :
    After adding the TXT records and the TXT records are verified, go back to the terminal where New-PACertificate is running and press Enter to continue, Posh-ACME will verify the records and, if successful, generate the certificate.

Troubleshooting Validation

To top

  • If the any of the TXT records for _acme-challenge.yourdomain.eu or _acme-challenge.www.yourdomain.eu is missing:
    • Double-check your DNS entry for typos.
    • Wait for propagation (up to 1 hour for some DNS providers).
    • Clear local DNS cache using: ipconfig /flushdns

After a while, in the original terminal, Let’s encrypt will create the SSL certificate and return something like:

Please remove the following TXT records:
------------------------------------------
_acme-challenge.<yourdomain.eu> -> cK9yLugh19_kk2u-DGkb4Wvh6sRql1qq7lrjNwAxxxx
_acme-challenge.www.<yourdomain.eu> -> iGXBQsTpxKxUZVXIx6dEVDoOKyu5gR_JHDwgxxxxx
------------------------------------------


Subject     NotAfter              KeyLength Thumbprint                               AllSANs
-------     --------              --------- ----------                               -------
CN=<yourdomain.eu> 2/24/2025 11:37:38 AM 2048      A567A0E51E295A898D1240CB31CC8E2009xxxxx {<yourdomain.eu>, www.<yourdomain.eu>}

Congratulations! You’ve successfully generated a STAGING SSL certificate for <yourdomain.eu> using the Posh-ACME PowerShell module and Let’s Encrypt! 🎉


Cleanup After Validation

After Let’s Encrypt validates the DNS records, you will receive a success message and the certificate will be issued. Example:

Please remove the following TXT records:
------------------------------------------
_acme-challenge.yourdomain.eu
_acme-challenge.www.yourdomain.eu

Remove the TXT records from your DNS settings to keep your configuration clean.
  1. Confirm removal by running:
    • nslookup -q=txt _acme-challenge.<yourdomain.eu>
    • nslookup -q=txt _acme-challenge.www.<yourdomain.eu>
    • You should see a “Non-existent domain” error.

Here’s a summary of what you achieved:

  1. Installed Posh-ACME:
    • Ensured the environment is ready for ACME operations.
  2. Set the ACME Server to Staging:
    • Used Set-PAServer LE_STAGE to avoid hitting production rate limits during testing.
  3. Issued the Certificate:
    • Validated the domain by adding the required DNS TXT records.
    • Generated the SSL certificate files with details:
      • Subject (CN): <yourdomain.eu>
      • Expiration Date: 2/24/2025
      • Key Length: 2048-bit
      • Thumbprint: A567A0E51E295A898D1240CB31CC8E2009xxxxx
      • ALLSANSs: covers both <yourdomain.eu> and www.<yourdomain.eu>
  4. Success!

Your Staging SSL files will be at:

C:\Users\<YourUser>\AppData\Local\Posh-ACME\LE_STAGE\<OrderID>\<yourdomain.eu>

Advantages of DNS-01 Challenge

To top

The DNS-01 challenge is a highly secure and flexible way to validate domain ownership because:

  1. No Open Ports Required:
    • Unlike the HTTP-01 challenge, which requires an accessible web server on port 80, the DNS-01 challenge only involves modifying DNS records. This means you don’t need to expose your server to the internet during validation.
  2. Wildcard Certificates Support:
    • It’s the only ACME challenge type that supports issuing wildcard certificates (e.g., *.yourdomain.eu). This makes it ideal for securing subdomains without needing to validate each one individually.
  3. Works Anywhere:
    • It doesn’t matter where your website or application is hosted. As long as you have access to manage the DNS for your domain, you can use this method.
  4. Better for Multi-Server Environments:
    • If you’re managing multiple servers or cloud environments, you don’t need to configure validation for each server. You just handle DNS centrally.
  5. Seamless Automation:
    • When combined with DNS APIs (e.g., AWS Route 53, Cloudflare, etc.), the DNS-01 challenge can be fully automated, making certificate issuance and renewal a breeze.
  6. Enhanced Security:
    • By avoiding the need for HTTP/HTTPS traffic during validation, the DNS-01 challenge reduces the attack surface for potential vulnerabilities during the certificate issuance process.

Step 7: Switch to Production

To top

Once everything works in the staging environment, switch to the production server:

Open PowerShell as administrator.

Switch to the production ACME server:

Set-PAServer LE_PROD 

This ensures all subsequent certificate requests are made to the production Let’s Encrypt server, which issues certificates trusted by browsers.


Step 8: Confirm the ACME Server

To top

Verify that the ACME server is now set to production:

 Get-PAServer

The output should indicate the production server:
https://acme-v02.api.letsencrypt.org/directory


Step 9: Generate the Production(real) Certificate

To top

Request a new certificate for your domain, ensuring that it’s valid for production:

New-PACertificate -Domain <yourdomain.eu>,www.<yourdomain.eu> -AcceptTOS -Contact <email@yourdomain.eu>

This process will:

  • Validate your domain ownership (using DNS-01 or other challenges).
  • Generate the certificate files, including cert.cer, fullchain.cer, and cert.key.

You will have to repeat the process that you performed to get the staging SSL, to create a new TXT record at your DNS provider.

Great! You have successfully generated the certificate files using Posh-ACME, and they are stored in the directory:

C:\Users\<YourUsername>\AppData\Local\Posh-ACME\LE_PROD\<OrderID>\<yourdomain.eu>

Let’s go step-by-step to implement these certificates on NGINX while ensuring everything is clear and manageable.


Step 10: Understand the Files

To top

Once you’ve successfully generated your SSL certificates, it’s essential to understand the files provided in the output directory:

File Breakdown

  1. cert.cer
    • Description: This is your primary SSL certificate for the domain (e.g., yourdomain.eu).
    • Use in Nginx: Often referred to as the “server certificate.” It’s specified using the ssl_certificate directive.
  2. cert.key
    • Description: This is the private key corresponding to the SSL certificate. Keep it secure and private.
    • Use in Nginx: Referenced using the ssl_certificate_key directive. Never share this file or leave it exposed.
  3. fullchain.cer
    • Description: Combines the server certificate (cert.cer) with the intermediate CA certificates. Many configurations require this to ensure a complete certificate chain is sent to clients.
    • Use in Nginx: Use this in the ssl_certificate directive when clients (e.g., browsers) require the full chain for validation.
  4. **chain.cer / chain0.cer
    • Description: Contains only the intermediate CA certificates. Some setups might require this for specific verification purposes.
    • Use in Nginx: Not directly used unless required for advanced configurations or third-party tools.
  5. cert.pfx
    • Description: A PKCS#12 file that includes the certificate and private key (but not the chain). Typically used for Windows or environments requiring .pfx files.
    • Use in Nginx: Not directly applicable unless converted into a .pem file using tools like OpenSSL.
  6. fullchain.pfx
    • Description: Similar to cert.pfx, but also includes the full certificate chain. It’s commonly used in Windows-based or other environments requiring .pfx files.
    • Use in Nginx: Like cert.pfx, this needs to be converted to .pem if required.
  7. request.csr
    • Description: The Certificate Signing Request (CSR) file generated during the certificate creation process. Not used post-issuance.
    • Use in Nginx: Not used. Retain for record-keeping if you need to re-issue certificates.
  8. order.json
    • Description: Metadata for the ACME order, used internally by Posh-ACME.
    • Use in Nginx: Not applicable. Retain this for troubleshooting or renewal automation.


Step 11: Install OpenSSL on Windows

To top

  1. Download the Installer:
    • Go to the OpenSSL for Windows page.
    • Download the version that matches your system architecture:
      • Win64 OpenSSL v3.4.x for 64-bit Windows.
      • Win32 OpenSSL v3.4.x for 32-bit Windows (rare cases).
  2. Run the Installer:
    • Execute the downloaded installer as an administrator.
    • During installation:
      • Select the default options unless you have a specific requirement.
      • Choose where OpenSSL will be installed (e.g., C:\Program Files\OpenSSL-Win64).
  3. Add OpenSSL to the System Path:
    • Open System Properties:
      • Press Windows + R, type sysdm.cpl, and press Enter.
    • Go to the Advanced tab and click Environment Variables.
    • Under System Variables, locate the Path variable and click Edit.
    • Add the OpenSSL bin directory (e.g., C:\Program Files\OpenSSL-Win64\bin) to the Path.
    • Click OK to save and close.
  4. Verify Installation:
    • Open a new PowerShell window.
    • Type: openssl version
    • If installed correctly, you will see the OpenSSL version number.

Step 12: Prepare the Certificate for Nginx

To top

12.1 Locate Your Certificate Files

By now, your Let’s Encrypt certificate files should be in the following directory:

C:\Users\<YourUsername>\AppData\Local\Posh-ACME\LE_PROD\<OrderID>\<yourdomain.eu>

Files in this directory:

  • cert.cer: The SSL certificate for your domain.
  • cert.key: The private key for your SSL certificate.
  • fullchain.cer: Combines your SSL certificate and the intermediate CA.

12.2 Organize Certificates in a Subfolder

Navigate to the directory where the Let’s Encrypt certificate files are stored:

C:\Users\<YourUsername>\AppData\Local\Posh-ACME\LE_PROD\<OrderID>\<yourdomain.eu>

Create a subfolder under C:\Certs with your domain name:

mkdir C:\Certs\<yourdomain>

Copy the following files to the new folder:

cert.key → Copy to C:\Certs\<yourdomain>\cert.key
fullchain.cer → Copy to C:\Certs\<yourdomain>\fullchain.cer

After this step, the folder structure will look like this

C:\Certs\<yourdomain>\
 cert.key
 fullchain.cer

12.3 Verify the Files

To ensure the files are in the correct format and usable by Nginx, run the following commands in PowerShell:

Verify the SSL certificate file:

openssl x509 -in C:\Certs\<yourdomain>\fullchain.cer -text -noout

Verify the private key file:

openssl rsa -in C:\Certs\<yourdomain>\cert.key -check

Both commands should display details about the certificate and private key. If they do, you’re ready to move on.


Step 13: Configure Nginx to Serve HTTPS

To top

Update the Nginx configuration file located at C:\nginx\conf\nginx.conf to use the certificate files directly:

worker_processes  1;

events {
    worker_connections  1024;
}

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

    sendfile        on;

    keepalive_timeout  65;

    # HTTP Server Block (Port 80)
    server {
        listen       80;
        listen [::]:80; #IPv6
        #the http server now serves your domain
        server_name yourdomain.eu www.yourdomain.eu;

        location / {
            root   html;
            index  index.html index.htm;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
    
    # ===This is the current addition 
    
    # HTTPS Server Block (Port 443)
    server {
        listen 443 ssl;
        listen [::]:443 ssl; #IPv6
        #the https server now serves your domain over SSL
        server_name yourdomain.eu www.yourdomain.eu;

        # SSL Certificate Files
        ssl_certificate      C:/Certs/<yourdomain>/fullchain.cer;
        ssl_certificate_key  C:/Certs/<yourdomain>/cert.key;

        location / {
            root   html;
            index  index.html index.htm;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
    
    # ===Here ends the current addition 
}


Verification

After updating the configuration:

Test the Nginx Configuration:

cd C:\nginx
.\nginx.exe -t

This ensures the configuration is valid.

Restart Nginx:

Restart-Service nginx

Test the HTTPS Site: Open a browser and visit:

https://yourdomain.eu

Ensure the site loads securely with no certificate errors.


Step 14: Redirect HTTP to HTTPS

To top

To ensure that all traffic to your site is encrypted and securely served over HTTPS, configure Nginx to redirect HTTP requests to HTTPS.


Modify the Nginx Configuration

  1. Open the nginx.conf file located at: C:\nginx\conf\nginx.conf
  2. Add or modify the server block for port 80 to include a redirect directive:
 # HTTP Server Block (Port 80)
 server {
         listen       80;
         listen [::]:80; #IPv6
          #the http server now serves your domain
          server_name yourdomain.eu www.yourdomain.eu;
          
          # ===This is the current addition 
          
          # Redirect all HTTP traffic to HTTPS
          return 301 https://$host$request_uri;
          
          # ===Here ends the current addition 
        
    }

Explanation of the Directive

  • listen 80: Listens for incoming HTTP requests on port 80.
  • server_name: Specifies the domain names that this server block will handle.
  • return 301 https://$host$request_uri;: Issues a 301 (Permanent Redirect) to HTTPS, preserving the original host and request URI.

Restart Nginx to Apply Changes

After modifying the configuration, restart Nginx to apply the changes:

Open PowerShell as Administrator.

Run the following commands:

Restart-Service nginx

Step 15: Test the Redirect

To top

15.1 Steps to Test

  1. Open your browser and navigate to:
    • http://yourdomain.eu → Should redirect to https://yourdomain.eu.
    • https://yourdomain.eu → Should load directly without any redirection issues.

15.2 Verify the Redirect

  1. Use a tool like Redirect Checker to confirm the redirect.
  2. Alternatively, use browser developer tools (F12):
    • Go to the Network tab.
    • Watch the HTTP request redirect to HTTPS with a 301 Permanent Redirect.

15.3 Handling Mixed Content by Rewriting HTTP Resources to HTTPS with Nginx

Problem: The site is served over HTTPS, but some resources (e.g., images, scripts) are loaded over HTTP.

We’ll address a scenario where your website loads resources (e.g., JavaScript or images) from an external site that only serves content over HTTP. Since we cannot modify the resource URLs in the HTML file, we will use Nginx to dynamically rewrite the http:// URLs to https://.

Problem Setup

Example index.html File

This file is hosted on your Nginx server and includes external resources that are served over HTTP:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Mixed Content Test</title>
</head>
<body>
    <h1>Mixed Content Test</h1>
    <!-- JavaScript loaded over HTTP -->
    <script src="http://external-http-only-site.com/script.js"></script>
    <!-- Image loaded over HTTP -->
    <img src="http://external-http-only-site.com/image.jpg" alt="Insecure Image">
</body>
</html>

When this page is loaded over HTTPS, your browser will display mixed content warnings and potentially block these HTTP resources.

Nginx Configuration to Rewrite HTTP to HTTPS

To dynamically rewrite all http:// resource URLs to https://, we will use Nginx’s sub_filter directive.

Updated Nginx Configuration

  1. Open your Nginx configuration file (e.g., nginx.conf or your site-specific configuration file).
  2. Add the following inside the HTTPS server block:
  # HTTPS Server Block (Port 443)
    server {
        listen 443 ssl;
        listen [::]:443 ssl; #IPv6
        #the https server now serves your domain over SSL
        server_name yourdomain.eu www.yourdomain.eu;

        # SSL Certificate Files
        ssl_certificate      C:/Certs/<yourdomain>/fullchain.cer;
        ssl_certificate_key  C:/Certs/<yourdomain>/cert.key;

        location / {
            root   html;
            index  index.html index.htm;
            
        # ===This is the current addition    
        
        # Rewrite HTTP resources to HTTPS
        sub_filter 'http://' 'https://';
        sub_filter_once off;

        # Ensure sub_filter works properly
        proxy_set_header Accept-Encoding "";
        
        # ===Here ends the current addition
        
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

Restart Nginx

After updating your configuration file, restart the Nginx service to apply the changes:

Restart-Service nginx

Test the Setup

  1. Load your page in a browser over HTTPS: https://yourdomain.eu
  2. Open the developer tools (F12 → Console tab) to verify that:
    • The external resources are now being requested over HTTPS.
    • No mixed content warnings are displayed.

Explanation of Configuration

  • sub_filter 'http://' 'https://'; Replaces all occurrences of http:// with https:// in the HTML response body.
  • sub_filter_once off; Ensures all occurrences of http:// are replaced, not just the first instance.
  • proxy_set_header Accept-Encoding ""; Disables compression (like gzip) for the response so that the sub_filter directive can work on the uncompressed content.

15.4. High Latency or Slow Site Loading

Problem: The site is slow to load, especially after enabling HTTPS.

Solution:

Enable caching for SSL sessions, add the following directives within the HTTPS server block:

 # HTTPS Server Block (Port 443)
    server {
        listen 443 ssl;
        listen [::]:443 ssl; #IPv6
        #the https server now serves your domain over SSL
        server_name yourdomain.eu www.yourdomain.eu;

        # SSL Certificate Files
        ssl_certificate      C:/Certs/<yourdomain>/fullchain.cer;
        ssl_certificate_key  C:/Certs/<yourdomain>/cert.key;
        
        # ===This is the current addition
    
        # Enable SSL Session Caching
        ssl_session_cache shared:SSL:1m;
        ssl_session_timeout 5m;
    
        # ===Here ends the current addition

        location / {
            root   html;
            index  index.html index.htm;
        
        # Rewrite HTTP resources to HTTPS
        sub_filter 'http://' 'https://';
        sub_filter_once off;

        # Ensure sub_filter works properly
        proxy_set_header Accept-Encoding "";        
                              
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
  • Explanation:
    • ssl_session_cache shared:SSL:1m;: Creates a shared cache for storing SSL sessions, reducing the overhead of repeated handshakes.
    • ssl_session_timeout 5m;: Sets the session timeout to 5 minutes, allowing for reusability within this period.

Restart Nginx

After updating your configuration file, restart the Nginx service to apply the changes:

Restart-Service nginx

Step 16: Configure Security Headers

To top

Configuring security headers ensures that your site is protected against common vulnerabilities such as cross-site scripting (XSS), clickjacking, and other attacks. These headers are implemented in the Nginx configuration file and are sent with every HTTP/HTTPS response.


16.1 Add Security Headers to the Nginx Configuration

Open your Nginx configuration file: C:\nginx\conf\nginx.conf

Update the server block for HTTPS (listen 443 ssl) to include the following security headers:

# HTTPS Server Block (Port 443)
    server {
        listen 443 ssl;
        listen [::]:443 ssl; #IPv6
        #the https server now serves your domain over SSL
        server_name yourdomain.eu www.yourdomain.eu;

        # SSL Certificate Files
        ssl_certificate      C:/Certs/<yourdomain>/fullchain.cer;
        ssl_certificate_key  C:/Certs/<yourdomain>/cert.key;
                   
        # Enable SSL Session Caching
        ssl_session_cache shared:SSL:1m;
        ssl_session_timeout 5m;
    
        # ===This is the current addition
        
        # Security Headers
        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
        add_header X-Content-Type-Options "nosniff" always;
        add_header X-Frame-Options "DENY" always;
        add_header X-XSS-Protection "1; mode=block" always;
        add_header Referrer-Policy "strict-origin-when-cross-origin" always;
        add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self';" always;
        add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
        
        # Cross-Origin Headers
        add_header Cross-Origin-Embedder-Policy "require-corp" always;
        add_header Cross-Origin-Opener-Policy "same-origin" always;
        add_header Cross-Origin-Resource-Policy "same-origin" always;
    
        # ===Here ends the current addition

        location / {
            root   html;
            index  index.html index.htm;
        
        # Rewrite HTTP resources to HTTPS
        sub_filter 'http://' 'https://';
        sub_filter_once off;

        # Ensure sub_filter works properly
        proxy_set_header Accept-Encoding "";        
                              
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

16.2 Explanation of Each Header

1. Strict-Transport-Security (HSTS)

  • Enforces HTTPS connections across all subdomains and encourages browser preloading.
  • Example: add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

2. X-Content-Type-Options

  • Prevents browsers from MIME-sniffing the content type.
  • Example: add_header X-Content-Type-Options "nosniff" always;

3. X-Frame-Options

  • Prevents clickjacking attacks by disabling framing.
  • Example: add_header X-Frame-Options "DENY" always;

4. X-XSS-Protection

  • Enables built-in cross-site scripting (XSS) protection in browsers.
  • Example: add_header X-XSS-Protection "1; mode=block" always;

5. Referrer-Policy

  • Limits the information shared in the Referer header to protect privacy.
  • Example: add_header Referrer-Policy "strict-origin-when-cross-origin" always;

6. Content-Security-Policy (CSP)

  • Restricts the resources (e.g., scripts, styles) the browser can load.
  • Example: add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self';" always;

7. Permissions-Policy

  • Controls browser APIs like geolocation, microphone, and camera.
  • Example: add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;

8. Cross-Origin Headers

  • Cross-Origin-Embedder-Policy: Prevents loading non-CORS-compliant resources. add_header Cross-Origin-Embedder-Policy "require-corp" always;
  • Cross-Origin-Opener-Policy: Isolates browsing contexts. add_header Cross-Origin-Opener-Policy "same-origin" always;
  • Cross-Origin-Resource-Policy: Restricts cross-origin resource sharing. add_header Cross-Origin-Resource-Policy "same-origin" always;

16.3 Test the Headers

  1. Verify Header Configuration:
    • After saving the changes, test the Nginx configuration: cd C:\nginx, .\nginx.exe -t
    • Restart Nginx to apply the changes: Restart-Service nginx
  2. Test Headers in the Browser:
    • Open the browser’s developer tools (F12).
    • Go to the Network tab and reload the site.
    • Click on any request and inspect the Headers section to confirm the security headers are present.
  3. Use Online Tools:

16.4 Adjust Headers for Your Application

If your application requires specific external resources (e.g., CDN-hosted scripts), modify the Content-Security-Policy to allow them. Example:

add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://cdnjs.cloudflare.com; style-src 'self';" always;<br>

With these security headers in place, your Nginx server will be much more resilient against common web application vulnerabilities.


Step 16.5: Implement a Security.txt File

Why Create a security.txt?

A security.txt file is a standardized way for organizations to provide security researchers with the appropriate contact information for reporting vulnerabilities. It enhances your site’s security and trustworthiness by offering a clear communication channel.

It:

  • Encourages responsible disclosure.
  • Provides a clear path for security researchers to report vulnerabilities.
  • Demonstrates commitment to security best practices.

Download and install from gpg4win.org, including Kleopatra.


16.5.1: Create a PGP Key in Kleopatra

Kleopatra, included with Gpg4win, is a GUI for managing PGP keys. Here’s how to create a key:

  1. Launch Kleopatra:
    • Open the Kleopatra application from your start menu.
  2. Create a New Key Pair:
    • Click on File > New Certificate or Certificates > New Certificate.
  3. Select OpenPGP Key Pair:
    • Choose the Create a personal OpenPGP key pair option and click Next.
  4. Fill in the Details:
    • Name: Enter your full name (e.g., Security Team).
    • Email: Enter your security contact email (e.g., security@youdomain.eu).
    • Ensure the checkbox for Protect the generated key with a passphrase is selected.
  5. Advanced Options (Optional):
    • Click Advanced Settings and select:
      • Key Material: Curve25519 (default for modern cryptography).
      • Expiration: Set a reasonable validity period (e.g., 3-5 years).
  6. Generate the Key:
    • Click Create Key. Kleopatra will prompt you to set a passphrase for your private key. Choose a strong passphrase and store it securely.
  7. Export Your Public Key:
    • After creating the key, right-click on your new certificate and select Export.
    • Save the public key as C:\Nginx\html\.well-known\security.txt.asc.

16.5.2: Create the Security.txt File

Navigate to your Nginx web root directory:

cd C:\nginx\html

Create a .well-known directory:

mkdir .well-known

Create and edit the security.txt

notepad .well-known/security.txt

You can use https://securitytxt.org/ to create the content or edit and add the following template content:

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256

Contact: mailto:security@youdomain.eu
Encryption: https://youdomain.eu/.well-known/security.txt.asc
Expires: 2025-12-31T23:59:59.000Z
Canonical: https://youdomain.eu/.well-known/security.txt
Policy: https://youdomain.eu/security-policy.html
Preferred-Languages: en

If you use https://securitytxt.org/ make sure to Manually add

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256

on top of the generated text.

16.5.3: Sign the Security.txt File

Navigate to the .well-known Directory
Open PowerShell and navigate to the directory where security.txt is stored:

cd C:\Nginx\html\.well-known

Create a Detached Signature for security.txt
Use GPG to create a detached signature for the file:

gpg -u security@youdomain.eu --output security.txt.sig --armor --detach-sig security.txt
  • -u security@youdomain.eu: Specifies the key to use for signing.
  • --output security.txt.sig: Saves the signature in the file security.txt.sig.
  • --armor: Ensures the signature is ASCII-armored for readability.
  • --detach-sig: Creates a detached signature, keeping the original file unchanged.

Verify the Signature
Confirm the signature is valid and matches the security.txt

gpg --verify security.txt.sig security.txt

Expected Output:

gpg: Signature made ... gpg: Good signature from "security@youdomain.eu"

Append the Signature to security.txt
Merge the detached signature into the security.txt file to create a clearsigned document:

Get-Content .\security.txt.sig | Add-Content .\security.txt

Your security.txt now should look something like this:

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256

Contact: mailto:security@youdomain.eu
Encryption: https://youdomain.eu/.well-known/security.txt.asc
Expires: 2025-12-31T23:59:59.000Z
Canonical: https://youdomain.eu/.well-known/security.txt
Policy: https://youdomain.eu/security-policy.html
Preferred-Languages: en

-----BEGIN PGP SIGNATURE-----

[Your PGP Signature Here]

-----END PGP SIGNATURE-----

16.5.4: Configure Nginx to Serve Security.txt

Update your Nginx configuration to serve the security.txt file with proper headers:

Open C:\nginx\conf\nginx.conf.

Add the following block inside the HTTPS server section:


# HTTPS Server Block (Port 443)
    server {
        listen 443 ssl
        listen [::]:443 ssl; #IPv6;
        #the https server now serves your domain over SSL
        server_name yourdomain.eu www.yourdomain.eu;

        # SSL Certificate Files
        ssl_certificate      C:/Certs/<yourdomain>/fullchain.cer;
        ssl_certificate_key  C:/Certs/<yourdomain>/cert.key;
                   
        # Enable SSL Session Caching
        ssl_session_cache shared:SSL:1m;
        ssl_session_timeout 5m;    
               
        # Security Headers
        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
        add_header X-Content-Type-Options "nosniff" always;
        add_header X-Frame-Options "DENY" always;
        add_header X-XSS-Protection "1; mode=block" always;
        add_header Referrer-Policy "strict-origin-when-cross-origin" always;
        add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self';" always;
        add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
        
        # Cross-Origin Headers
        add_header Cross-Origin-Embedder-Policy "require-corp" always;
        add_header Cross-Origin-Opener-Policy "same-origin" always;
        add_header Cross-Origin-Resource-Policy "same-origin" always;
        
        # ===This is the current addition
        
        # Serve the .well-known directory for security.txt and related files
        location ^~ /.well-known/ {
            root C:/Nginx/html;
            default_type text/plain;
            allow all;

            # Override for .asc files within .well-known
            location ~* \.asc$ {
                default_type application/pgp-signature;
                add_header Cache-Control "no-store";
                add_header X-Content-Type-Options "nosniff" always;
            }
        }
        # ===Here ends the current addition

        location / {
            root   html;
            index  index.html index.htm;
        
        # Rewrite HTTP resources to HTTPS
        sub_filter 'http://' 'https://';
        sub_filter_once off;

        # Ensure sub_filter works properly
        proxy_set_header Accept-Encoding "";        
                              
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

Save the file.

16.5.5: Test the Security.txt Setup

Restart Nginx: Restart-Service nginx

Use curl to test the availability of both the security.txt and the PGP signature:

Verify the content of security.txt:

curl -v https://youdomain.eu/.well-known/security.txt

Verify the content of the PGP signature:

curl -v https://youdomain.eu/.well-known/security.txt.asc

Test both files for accessibility: Check that:

security.txt returns 200 OK with text/plain as the Content-Type.

security.txt.asc returns 200 OK with application/pgp-signature as the Content-Type.

Use online validators like https://web-check.xyz/ to ensure proper implementation.

16.5.6: Why It’s Important

  • Enhances trust by providing clear security contact details.
  • Aligns with security standards like ISO 29147.
  • Simplifies the vulnerability disclosure process for researchers.

Step 17: Disable Weak Protocols and Ciphers

To top

To ensure that your Nginx server uses secure SSL/TLS protocols and ciphers, explicitly disable older, insecure versions (e.g., SSLv3, TLS 1.0/1.1). This protects your server from vulnerabilities such as BEAST, POODLE, and others.

17.1 Update the Nginx Configuration

Open your Nginx configuration file: File Path: C:\nginx\conf\nginx.conf

Add the following settings in the HTTPS (listen 443 ssl) server block:

 # HTTPS Server Block (Port 443)
    server {
        listen 443 ssl;
        listen [::]:443 ssl; #IPv6
        #the https server now serves your domain over SSL
        server_name yourdomain.eu www.yourdomain.eu;

        # SSL Certificate Files
        ssl_certificate      C:/Certs/<yourdomain>/fullchain.cer;
        ssl_certificate_key  C:/Certs/<yourdomain>/cert.key;
                   
        # Enable SSL Session Caching
        ssl_session_cache shared:SSL:1m;
        ssl_session_timeout 5m;
        
        # ===This is the current addition
        
        # Disable weak SSL protocols
        ssl_protocols TLSv1.2 TLSv1.3;

        # Use secure ciphers
        ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';

        # Enforce strong SSL settings
        ssl_prefer_server_ciphers on;

        # ===Here ends the current addition
            
        # Security Headers
        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
        add_header X-Content-Type-Options "nosniff" always;
        add_header X-Frame-Options "DENY" always;
        add_header X-XSS-Protection "1; mode=block" always;
        add_header Referrer-Policy "strict-origin-when-cross-origin" always;
        add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self';" always;
        add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
        
        # Cross-Origin Headers
        add_header Cross-Origin-Embedder-Policy "require-corp" always;
        add_header Cross-Origin-Opener-Policy "same-origin" always;
        add_header Cross-Origin-Resource-Policy "same-origin" always;
        
        # Serve the .well-known directory for security.txt and related files
        location ^~ /.well-known/ {
            root C:/Nginx/html;
            default_type text/plain;
            allow all;

            # Override for .asc files within .well-known
            location ~* \.asc$ {
                default_type application/pgp-signature;
                add_header Cache-Control "no-store";
                add_header X-Content-Type-Options "nosniff" always;
            }
        }
    
        location / {
            root   html;
            index  index.html index.htm;
        
        # Rewrite HTTP resources to HTTPS
        sub_filter 'http://' 'https://';
        sub_filter_once off;

        # Ensure sub_filter works properly
        proxy_set_header Accept-Encoding "";        
                              
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    } 

17.2 Explanation of Settings

  • ssl_protocols TLSv1.2 TLSv1.3;: Ensures that only TLS 1.2 and TLS 1.3 are enabled while disabling weaker protocols like SSLv3 and TLS 1.0/1.1.
  • ssl_ciphers: Lists strong, modern ciphers prioritized for secure key exchange methods like ECDHE (Elliptic Curve Diffie-Hellman Ephemeral).
  • ssl_prefer_server_ciphers on;: Forces the server to prefer its own cipher list over the client’s, ensuring a stronger connection.

17.3 Test the Configuration

  • Check the syntax for errors: cd C:\nginx, .\nginx.exe -t
  • Reload Nginx to apply the changes: Restart-Service nginx

17.4 Verify Protocol and Cipher Settings

  • Use OpenSSL to confirm which protocols and ciphers are supported:
    • openssl s_client -connect yourdomain.eu:443 -tls1_2 openssl s_client -connect yourdomain.eu:443 -tls1_1
      • TLS 1.2 should succeed.
      • TLS 1.1 should fail if properly disabled.
  • Run the following openssl command to verify the cipher suites offered by your server:
    openssl s_client -connect yourdomain.eu:443 -cipher <cipher_name>
    Replace <cipher_name> with a specific cipher (e.g., AES128-SHA or ECDHE-RSA-A
  • Use SSL Labs to verify your configuration and check for weak protocols or ciphers.

17.5 Best Practices

  • Regularly review and update the cipher list to reflect current security standards. You can use tools like Mozilla’s SSL Configuration Generator.
  • Ensure that your server supports TLS 1.3 for improved speed and security.

Step 18: Enable OCSP Stapling

To top

Online Certificate Status Protocol (OCSP) stapling improves SSL/TLS performance and security by allowing your server to provide proof of a certificate’s validity, rather than having clients query the Certificate Authority (CA) directly. This reduces latency and enhances privacy.

Here’s how to enable OCSP stapling in Nginx:


18.1 Verify OCSP Support for Your Certificate

Before proceeding, ensure your SSL certificate supports OCSP stapling. You can verify this using OpenSSL:

openssl x509 -in C:/Certs/<yourdomain>/fullchain.cer -text -noout | findstr "OCSP"

Look for an OCSP URI in the output. If present, your certificate supports OCSP stapling.


18.2 Configure OCSP Stapling in Nginx

Update your Nginx configuration file (e.g., C:\nginx\conf\nginx.conf) to include OCSP stapling settings in the HTTPS server block:

 # HTTPS Server Block (Port 443)
    server {
        listen 443 ssl;
        listen [::]:443 ssl; #IPv6
        #the https server now serves your domain over SSL
        server_name yourdomain.eu www.yourdomain.eu;

        # SSL Certificate Files
        ssl_certificate      C:/Certs/<yourdomain>/fullchain.cer;
        ssl_certificate_key  C:/Certs/<yourdomain>/cert.key;
                   
        # Enable SSL Session Caching
        ssl_session_cache shared:SSL:1m;
        ssl_session_timeout 5m;        
                
        # Disable weak SSL protocols
        ssl_protocols TLSv1.2 TLSv1.3;

        # Use secure ciphers
        ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';

        # Enforce strong SSL settings
        ssl_prefer_server_ciphers on;
        
        # ===This is the current addition
        
        # OCSP Stapling
        ssl_stapling         on;
        ssl_stapling_verify  on;
        resolver             8.8.8.8 8.8.4.4 valid=300s;
        resolver_timeout     5s;
        # ===Here ends the current addition
            
        # Security Headers
        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
        add_header X-Content-Type-Options "nosniff" always;
        add_header X-Frame-Options "DENY" always;
        add_header X-XSS-Protection "1; mode=block" always;
        add_header Referrer-Policy "strict-origin-when-cross-origin" always;
        add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self';" always;
        add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;;
        
        # Cross-Origin Headers
        add_header Cross-Origin-Embedder-Policy "require-corp" always;
        add_header Cross-Origin-Opener-Policy "same-origin" always;
        add_header Cross-Origin-Resource-Policy "same-origin" always;
        
        # Serve the .well-known directory for security.txt and related files
        location ^~ /.well-known/ {
            root C:/Nginx/html;
            default_type text/plain;
            allow all;

            # Override for .asc files within .well-known
            location ~* \.asc$ {
                default_type application/pgp-signature;
                add_header Cache-Control "no-store";
                add_header X-Content-Type-Options "nosniff" always;
            }
        }
    
        location / {
            root   html;
            index  index.html index.htm;
        
        # Rewrite HTTP resources to HTTPS
        sub_filter 'http://' 'https://';
        sub_filter_once off;

        # Ensure sub_filter works properly
        proxy_set_header Accept-Encoding "";        
                              
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    } 

18.3 Explanation of OCSP Directives

  • ssl_stapling on;: Enables OCSP stapling.
  • ssl_stapling_verify on;: Ensures the OCSP response is valid and trustworthy.
  • resolver 8.8.8.8 8.8.4.4 valid=300s;: Specifies DNS resolvers (e.g., Google Public DNS) to resolve the OCSP responder’s domain.
  • resolver_timeout 5s;: Sets a timeout for resolver queries.

18.4 Test Your Configuration

Validate the Nginx Configuration: cd C:\nginx, .\nginx.exe -t Ensure the output states that the configuration is valid.

Reload Nginx: Restart-Service nginx

Verify OCSP Stapling: Use OpenSSL to confirm that OCSP stapling is working:

openssl s_client -connect yourdomain:443 -status

Look for a section labeled OCSP response: in the output. If stapling is enabled, you’ll see a valid OCSP response.


18.5 Verify Using Online Tools

You can also use SSL test tools like SSL Labs to confirm OCSP stapling is correctly configured.

  • Visit SSL Labs.
  • Enter your domain name and run the test.
  • Check the “OCSP Stapling” section in the results.

Optional: Troubleshooting OCSP Stapling

If OCSP stapling doesn’t work, try these steps:

  1. Verify the Certificate: Ensure the certificate and chain files are correct and support OCSP.
  2. Check Firewall Rules: Ensure outgoing requests to the OCSP responder (usually on port 80) are allowed.
  3. Review Logs: Enable debug logging in Nginx: error_log logs/error.log debug; Check the logs for errors related to OCSP stapling.
  4. Test the Resolver: Confirm that the specified DNS resolvers (e.g., Google DNS) are accessible and working.

Step 19: Add CAA Records in DNS:

To top

If your domain does not have a CAA (Certificate Authority Authorization) record, it means you have not explicitly restricted which CAs are allowed to issue certificates for your domain.

Adding CAA records is a best practice to improve security by reducing the risk of unauthorized certificates being issued, read our in depth analysis: No CAA = Open Door for Fraud: Why CAA Records Should Be Mandatory

Add CAA records to your DNS zone file to specify which CAs can issue certificates for your domain. For example:

<yourdomain.eu>. IN CAA 0 issue "letsencrypt.org"
<yourdomain.eu>. IN CAA 0 issuewild "letsencrypt.org"

This ensures only Let’s Encrypt can issue certificates for your domain.


Step 20: Nginx Performance

To top

Adding “Expires headers” to improve caching can further optimize your site for speed. This step can reduce unnecessary HTTP requests on repeat visits.

  1. Enable HTTP/2:
    • If you are not already using HTTP/2, consider enabling it for better performance and security.
  2. Gzip Compression:
    • Improves load time by reducing file sizes.
    • Configured to compress text, CSS, JavaScript, and XML responses.
  3. Expires Headers:
    • Enhances caching for static resources like images, CSS, and JavaScript.
    • Configured to cache these resources for 30 days to reduce repeated HTTP requests.
  4. Minify CSS and JavaScript
    • Use tools or plugins to minify your CSS and JavaScript files before deploying them to production.
 # HTTPS Server Block (Port 443)
    server {
        listen 443 ssl;
        listen [::]:443 ssl; #IPv6
        # ===This is the current addition
        
        http2 on; # Enable HTTP/2 explicitly
        
        # ===Here ends the current addition
        
        #the https server now serves your domain over SSL
        server_name yourdomain.eu www.yourdomain.eu;

        # SSL Certificate Files
        ssl_certificate      C:/Certs/<yourdomain>/fullchain.cer;
        ssl_certificate_key  C:/Certs/<yourdomain>/cert.key;
                   
        # Enable SSL Session Caching
        ssl_session_cache shared:SSL:1m;
        ssl_session_timeout 5m;        
                
        # Disable weak SSL protocols
        ssl_protocols TLSv1.2 TLSv1.3;

        # Use secure ciphers
        ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';

        # Enforce strong SSL settings
        ssl_prefer_server_ciphers on;        
              
        # OCSP Stapling
        ssl_stapling         on;
        ssl_stapling_verify  on;
        resolver             8.8.8.8 8.8.4.4 valid=300s;
        resolver_timeout     5s;
                  
        # Security Headers
        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
        add_header X-Content-Type-Options "nosniff" always;
        add_header X-Frame-Options "DENY" always;
        add_header X-XSS-Protection "1; mode=block" always;
        add_header Referrer-Policy "strict-origin-when-cross-origin" always;
        add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self';" always;
        add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;;
        
        # Cross-Origin Headers
        add_header Cross-Origin-Embedder-Policy "require-corp" always;
        add_header Cross-Origin-Opener-Policy "same-origin" always;
        add_header Cross-Origin-Resource-Policy "same-origin" always;
        
        # Serve the .well-known directory for security.txt and related files
        location ^~ /.well-known/ {
            root C:/Nginx/html;
            default_type text/plain;
            allow all;

            # Override for .asc files within .well-known
            location ~* \.asc$ {
                default_type application/pgp-signature;
                add_header Cache-Control "no-store";
                add_header X-Content-Type-Options "nosniff" always;
            }
        }
        
        # ===This is the current addition
        
        # Enable Gzip Compression
        gzip on;
        gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
        gzip_min_length 1024;

        # Add Expires Headers for caching
        location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
            expires 30d;
            add_header Cache-Control "public";
        }

        # ===Here ends the current addition
    
        location / {
            root   html;
            index  index.html index.htm;
        
        # Rewrite HTTP resources to HTTPS
        sub_filter 'http://' 'https://';
        sub_filter_once off;

        # Ensure sub_filter works properly
        proxy_set_header Accept-Encoding "";        
                              
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    } 

Testing:

  1. Verify Configuration: cd C:\nginx, .\nginx.exe -t
  2. Restart Nginx: Restart-Service nginx
  3. Test Results:

The full Nginx.conf file:

worker_processes  1;
error_log C:/nginx/logs/error.log debug;

events {
    worker_connections  1024;
}

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

    sendfile        on;

    keepalive_timeout  65;

      
    # HTTP Server Block (Port 80)
    server {
        listen       80;
        listen [::]:80;
        #
        listen       8080;
        listen [::]:8080;
        #the http server now serves your domain
        server_name yourdomain.eu www.yourdomain.eu;
        
        # Redirect all HTTP traffic to HTTPS
        return 301 https://$host$request_uri;

        location / {
            root   html;
            index  index.html index.htm;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
    
 # HTTPS Server Block (Port 443)
    server {
        listen 443 ssl;
        listen [::]:443 ssl;
        #
        listen 8443 ssl;
        listen [::]:8443 ssl;
        http2 on; # Enable HTTP/2 explicitly
        #the https server now serves your domain over SSL
        server_name yourdomain.eu www.yourdomain.eu;

        # SSL Certificate Files
        ssl_certificate      C:/Certs/<yourdomain>/fullchain.cer;
        ssl_certificate_key  C:/Certs/<yourdomain>/cert.key;

        # Enable SSL Session Caching
        ssl_session_cache shared:SSL:1m;
        ssl_session_timeout 5m;        

        # Disable weak SSL protocols
        ssl_protocols TLSv1.2 TLSv1.3;

        # Use secure ciphers
        ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';

        # Enforce strong SSL settings
        ssl_prefer_server_ciphers on;
        
        # OCSP Stapling
        ssl_stapling         on;
        ssl_stapling_verify  on;
        resolver             8.8.8.8 8.8.4.4 valid=300s;
        resolver_timeout     5s;
        
        # Security Headers
        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
        add_header X-Content-Type-Options "nosniff" always;
        add_header X-Frame-Options "DENY" always;
        add_header X-XSS-Protection "1; mode=block" always;
        add_header Referrer-Policy "strict-origin-when-cross-origin" always;
        add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self';" always;
        add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;

        # Cross-Origin Headers
        add_header Cross-Origin-Embedder-Policy "require-corp" always;
        add_header Cross-Origin-Opener-Policy "same-origin" always;
        add_header Cross-Origin-Resource-Policy "same-origin" always;

        # ===This is the current addition  

        # Serve the .well-known directory for security.txt and related files
        location ^~ /.well-known/ {
            root C:/Nginx/html;
            default_type text/plain;
            allow all;

            # Override for .asc files within .well-known
            location ~* \.asc$ {
                default_type application/pgp-signature;
                add_header Cache-Control "no-store";
                add_header X-Content-Type-Options "nosniff" always;
            }
        }

        # ===Here ends the current addition 

        # Enable Gzip Compression
        gzip on;
        gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
        gzip_min_length 1024;

        # Add Expires Headers for caching
        location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
            expires 30d;
            add_header Cache-Control "public";
        }
            
        location / {
            root   html;
            index  index.html index.htm;
        
            # Rewrite HTTP resources to HTTPS
            sub_filter 'http://' 'https://';
            sub_filter_once off;

            # Ensure sub_filter works properly
            proxy_set_header Accept-Encoding "";        
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    } 
}

Step 21: Test Your Configuration

To top

This final step ensures that all the configurations applied in the previous steps are functioning correctly, including HTTPS, redirections, security headers, OCSP stapling, and DNS settings. Follow these sub-steps to verify your setup.

21.1 Test the HTTPS Connection

  1. Open a web browser and visit your site:
    • https://yourdomain.eu
    • Ensure there are no SSL warnings or errors.
    • Verify that the certificate is valid and matches your domain.
  2. Test HTTP redirection:

21.2 Verify Security Headers

Use a security headers testing tool to confirm your headers are configured properly:

  • Visit Security Headers.
  • Enter your domain and run the test.
  • Check the results to ensure all expected headers (e.g., Content-Security-Policy, Strict-Transport-Security, X-Frame-Options, etc.) are present and configured correctly.

21.3 Validate OCSP Stapling

Confirm that OCSP stapling is working as expected:

  1. Run the following OpenSSL command in PowerShell: openssl s_client -connect yourdomain.eu:443 -status
  2. Look for the OCSP response: section in the output. If OCSP stapling is working, you’ll see a valid response.
  3. Alternatively, test with SSL Labs:
    • Visit SSL Labs.
    • Enter your domain and start the test.
    • Confirm that OCSP stapling is enabled in the results.

21.4 Check CAA Records

Verify your CAA records using online DNS tools:

  • Use a tool like NsLookup CAA Checker.
  • Enter your domain to confirm the records match your configuration.

21.5 Browser Developer Tools

Use browser developer tools to check network activity and confirm:

  • Redirects are properly configured with 301 Moved Permanently status.
  • Security headers are applied to all requests.
  • No mixed content errors (e.g., HTTP resources loading on HTTPS pages).

21.6 Penetration Testing

Optionally, use tools like Nikto, Burp Suite, or OWASP ZAP to scan for vulnerabilities in your configuration.


21.7 Verify DNS Settings

Check your DNS records for:

  • Proper A/AAAA records pointing to your server’s IP.
  • Correct CAA records for the Certificate Authority.
  • The absence of DNS misconfigurations or errors using MXToolbox.

21.8 Automate Configuration Testing

For ongoing validation, consider using automated monitoring tools:

  • Set up uptime monitoring (e.g., Pingdom, UptimeRobot) for HTTPS.
  • Use SSL monitoring tools to track certificate expiration and security.

Step 22: Troubleshooting

To top

1. Nginx Won’t Start or Restart

Problem: Running Restart-Service nginx or starting Nginx results in an error.

Solution:

Verify the configuration syntax:

cd C:\nginx
.\nginx.exe -t

If there’s an error, the output will include details pointing to the problematic line in nginx.conf.

Check if the certificate and key paths are correct: Ensure that the paths specified in the configuration file match the actual file locations.

sl_certificate C:/Certs/<yourdomain>/fullchain.cer;
ssl_certificate_key C:/Certs/<yourdomain>/cert.key;

Check the logs:

Default error log: C:\nginx\logs\error.log

Look for SSL or syntax-related errors.


2. SSL Certificate Not Recognized

  • Problem: The browser shows a “Certificate Invalid” or “Not Secure” error.
  • Solution:
    1. Confirm that the certificate and key files are properly linked in nginx.conf.
    2. Ensure the certificate is for the correct domain. Use OpenSSL to inspect the certificate:
      • openssl x509 -in C:/Certs/yourdomain.eu/fullchain.cer -text -noout
    3. Check if the intermediate certificate is included. If missing, use the fullchain.cer file instead of cert.cer.

3. HTTP Traffic Not Redirecting to HTTPS

Problem: Navigating to http://yourdomain.eu does not redirect to https://yourdomain.eu.

Solution:

Verify the HTTP server block:

server { listen 80; 
server_name yourdomain.eu www.yourdomain.eu;
 # Redirect all HTTP traffic to HTTPS return 301
  https://$host$request_uri; }

Ensure there are no conflicting configurations or multiple server blocks for port 80.

Restart Nginx to apply the changes: Restart-Service nginx


4. Access Forbidden or Page Not Found (404)

  • Problem: The browser displays “403 Forbidden” or “404 Not Found” when accessing the site.
  • Solution:
    1. Verify the root directive in the HTTPS server block points to the correct directory:
      • root C:/nginx/html;
    2. Check the file permissions of the website files in C:/nginx/html.
    3. Ensure the index.html file exists in the specified directory.

5. Changes to nginx.conf Not Reflected

  • Problem: You made changes to nginx.conf, but they are not taking effect.
  • Solution:
    1. Restart Nginx to reload the configuration: Restart-Service nginx
    2. Verify that you are editing the correct configuration file: C:\nginx\conf\nginx.conf
    3. Check for syntax errors by running: cd C:\nginx, .\nginx.exe -t

6. Firewall Blocking HTTPS or HTTP

  • Problem: The site is not accessible externally.
  • Solution:
    1. Ensure the necessary firewall rules are added to allow traffic on ports 80 (HTTP) and 443 (HTTPS):
      • Add an inbound rule for Nginx: Allow program: C:\nginx\nginx.exe Ports: 80, 443
    2. Verify external connectivity using a tool like CanYouSeeMe to confirm your server is reachable on the specified ports.

7. Certificate Renewal Issues

  • Problem: Let’s Encrypt certificates are not renewing automatically.
  • Solution:
    1. Automate certificate renewal with a scheduled task to run Posh-ACME.
    2. Ensure the DNS TXT record is updated during renewal.
    3. Validate the renewal process with: New-PACertificate -Domain <yourdomain.eu> -Renew -Force

Advanced Debugging Techniques

When facing configuration or connection issues, advanced tools can provide valuable insights.

1. OpenSSL Testing

To test TLS versions or ciphers supported by your server:

openssl s_client -connect yourdomain.eu:443 -tls1_2
  • Replace -tls1_2 with -tls1_3 to test TLS 1.3.
  • Look for the SSL handshake result for success or errors.

2. Detailed Nginx Logs

Enable more detailed logs in nginx.conf:

http {
    log_format custom '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$request_time"';
    access_log /var/log/nginx/access.log custom;
}

3. Real-Time Debugging with nginx-debug

Start Nginx in debug mode:

nginx-debug -c /path/to/nginx.conf

Use this mode sparingly, as it generates verbose logs.

Notes

  • Keep the C:\nginx\logs\error.log file handy for troubleshooting more specific errors.
  • For advanced debugging, use tools like curl or browser developer tools to inspect HTTP/HTTPS requests.

Advanced Hardened Nginx Settings

While the default configurations you’ve implemented so far provide robust security, advanced users might want to further harden their Nginx setup to protect against specific threats like brute force attacks, DDoS, or unauthorized access. Below are some strategies to explore if you’re ready to take your security to the next level.


1. Rate Limiting (Prevent Brute Force Attacks)

Limit the number of requests from a single IP address to prevent brute force attacks or abusive traffic.

Example Configuration:

http {
    limit_req_zone $binary_remote_addr zone=rate_limit_zone:10m rate=10r/s;

    server {
        location / {
            limit_req zone=rate_limit_zone burst=20 nodelay;
        }
    }
}

2. DDoS Mitigation

Protect your server from Distributed Denial of Service (DDoS) attacks using tools and connection limits.

Use Monitoring Tools: Tools like Wazuh (for Windows) can help detect malicious patterns in logs.

Connection Limiting:

http { limit_conn_zone $binary_remote_addr zone=conn_limit:10m; server { location / { limit_conn conn_limit 10; } } }

3. IP Whitelisting

Restrict access to sensitive areas (like admin panels) by whitelisting trusted IPs.

Example Configuration:

server {
    location /admin {
        allow 192.168.1.1;
        deny all;
    }
}

Why Explore These?

These strategies are optional and advanced and require careful consideration and testing before deployment. They can add an extra layer of security but may also introduce complexities or limitations for legitimate users.

If you’re curious, consider exploring tools like: