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
- Step 1: Install PowerShell
- Step 2: Install Nginx on Windows
- Step 3: Install NSSM, run Nginx as a service
- Step 4: Install and Configure Posh-ACME
- Step 5: Configure Let’s Encrypt Staging Server
- Step 6: Generate a Staging(test) Certificate
- Step 7: Switch to Production
- Step 8: Confirm the ACME Server
- Step 9: Generate the Production(real) Certificate
- Step 10: Understand the Files
- Step 11: Install OpenSSL on Windows
- Step 12: Prepare the Certificate for Nginx
- Step 13: Configure Nginx to Serve HTTPS
- Step 14: Redirect HTTP to HTTPS
- Step 15: Test the Redirect
- Step 16: Configure Security Headers
- Step 17: Disable Weak Protocols and Ciphers
- Step 18: Enable OCSP Stapling
- Step 19: Add CAA Records in DNS
- Step 20: Nginx Performance
- Step 21: Test Your Configuration
- Step 22: Troubleshooting
Prerequisites
- 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: SelectA
(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: SelectAAAA
(AAAA Record).
Value: Enter your public IPv6 address.
TTL: Set to the default value (e.g., 3600 seconds).
CNAME Record forwww
Subdomain
Host: Enterwww
(represents thewww
subdomain, e.g.,www.yourdomain.eu
).
Type: SelectCNAME
.
Value: Enteryourdomain.eu
.
TTL: Set to the default value (e.g., 3600 seconds).
Example DNS Configuration:Host | Type | Value | TTL
@ | A | 203.x.1x3.2xx | 3600 @ | A
AAA
|<your-public-ipv6>
| 3600
www | CNAME | yourdomain.eu | 3600
0.4 Verify the DNS Records
Use a DNS lookup tool like DNSChecker or PowerShell’snslookup
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 foryourdomain.eu
and correctly pointwww.yourdomain.eu
toyourdomain.eu
.
Important: DNS changes may take up to 48 hours to propagate, but they usually update within minutes.
- Posh-ACME Module:
- We are going to use Posh-ACME to issue a Let’s Encrypt SSL, Certbot discontinued Windows support in February 2024
- 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.
- 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.
- Before starting, you should have the following folder structure on your
Step 1: Install PowerShell
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.
2.1 Download Nginx
- Visit the Official Nginx Download Page:
- Go to Nginx Official Download Page.
- 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.
- Under the “Stable version” section, find the link for the Windows binaries (e.g.,
2.2 Extract Nginx
- Create a Directory for Nginx:
- Open File Explorer and create a new folder where you’d like to install Nginx, e.g.,
C:\nginx
.
- Open File Explorer and create a new folder where you’d like to install Nginx, e.g.,
- 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 likeconf
,html
, andlogs
insideC:\nginx
.
- Right-click on the downloaded
2.3 Start Nginx
- Open PowerShell as Administrator:
- Press
Win + X
and select Windows PowerShell (Admin).
- Press
- Navigate to the Nginx Directory:
cd C:\nginx
- Start the Nginx Server:
.\nginx.exe
- 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
- Verify Nginx is Running:
- Open a web browser and navigate to
http://localhost
. - You should see the default Nginx welcome page.
- Open a web browser and navigate to
2.4 Configuring Nginx Firewall Rules on Windows Server 2022
2.4.1: Access Windows Defender Firewall
- Open Windows Defender Firewall with Advanced Security:
- Press
Win + R
, typewf.msc
, and hit Enter.
- Press
- Select Inbound Rules from the left-hand panel.
- Repeat the steps below for Outbound Rules after completing inbound configuration.
2.4.2: Create Inbound Rules
Rule for HTTP (Port 80)
- Click New Rule in the right-hand menu.
- Select Port and click Next.
- Choose TCP and specify Specific Local Ports as
80
. - Click Next and select “Allow the Connection” (no “if it is secure” option exists for inbound rules).
- Select all profiles: Domain, Private, and Public.
- Name the rule (e.g.,
Nginx HTTP Inbound
) and click Finish.
Rule for HTTPS (Port 443)
- Click New Rule in the right-hand menu.
- Select Port and click Next.
- Choose TCP and specify Specific Local Ports as
443
. - Click Next and select “Allow the Connection”.
- Select all profiles: Domain, Private, and Public.
- Name the rule (e.g.,
Nginx HTTPS Inbound
) and click Finish.
Rule for Nginx Executable
- Click New Rule in the right-hand menu.
- Select Program and click Next.
- Choose This Program Path and browse to the Nginx executable location:
- For example:
C:\nginx\nginx.exe
.
- For example:
- Click Next and select “Allow the Connection”.
- Select all profiles: Domain, Private, and Public.
- Name the rule (e.g.,
Nginx Executable Inbound
) and click Finish.
2.4.3: Create Outbound Rules
Rule for HTTP (Port 80)
- Click New Rule in the Outbound Rules section.
- Select Port and click Next.
- Choose TCP and specify Specific Local Ports as
80
. - Click Next and select “Allow the Connection”.
- Select all profiles: Domain, Private, and Public.
- Name the rule (e.g.,
Nginx HTTP Outbound
) and click Finish.
Rule for HTTPS (Port 443)
- Click New Rule in the Outbound Rules section.
- Select Port and click Next.
- Choose TCP and specify Specific Local Ports as
443
. - Click Next and select “Allow the connection if it is secure”.
- This ensures only secure (encrypted) outbound connections.
- Select all profiles: Domain, Private, and Public.
- 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:
- 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.
- Path MTU Discovery: ICMPv6 enables devices to determine the maximum transmission unit (MTU) for a path, preventing fragmentation and ensuring efficient data transfer.
- 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.
- Error Reporting: ICMPv6 reports errors in packet processing, such as:
- Destination unreachable.
- Packet too big.
- Time exceeded.
- 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
Type | Code | Name | Purpose |
---|---|---|---|
128 | 0 | Echo Request | Used by the ping command to test IPv6 connectivity. |
129 | 0 | Echo Reply | Reply to an Echo Request, confirming that the target is reachable. |
133 | 0 | Router Solicitation | Requests routers to send router advertisements for auto-configuration. |
134 | 0 | Router Advertisement | Used by routers to advertise their presence and configuration to devices. |
135 | 0 | Neighbor Solicitation | Similar to ARP in IPv4, used to discover neighbors and resolve MAC addresses. |
136 | 0 | Neighbor Advertisement | Used to respond to neighbor solicitations and to inform of changes in link-layer addresses. |
1 | Various | Destination Unreachable | Informs 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
- Restart the Nginx service to apply changes:
- Open Powershell as Administrator and run: cd
C:\nginx
,.\nginx.exe -s reload
- Open Powershell as Administrator and run: cd
- Test HTTP :
- Open a browser and navigate to:
http://your-server-ip
(for HTTP).
- Open a browser and navigate to:
- 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
- Place Your Website Content:
- Copy your website files (e.g.,
index.html
, images, stylesheets) into theC:\nginx\html
directory.
- Copy your website files (e.g.,
- Test the Content Locally:
- Ensure that
index.html
is present in thehtml
directory.
- Ensure that
2.7 Restart Nginx to Apply Changes
- 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.
- In the PowerShell, run:
2.8 Test Your Site
- Verify the Site Locally:
- Open a web browser and navigate to
http://localhost
orhttp://127.0.0.1
. - You should see your website’s homepage instead of the Nginx welcome page.
- Open a web browser and navigate to
- 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.
- If your domain’s DNS records point to your server’s IP address, you can test by navigating to <
- 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 tolocalhost
:- Open the
hosts
file located atC:\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
<
in your browser will load your local Nginx server.http://yourdomain.eu
>
- Open the
- If DNS is not yet set up, you can edit your local
Step 3: Download and Install NSSM, run Nginx as a service
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:
- That Nginx Processes are Running:
- Check if the master and worker processes are active.
- 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.
- If
Note:
After installing NSSM and running Nginx as a service, .\nginx -s stop and
will no longer work, you will have to use Stop-Service -Name Nginx and Restart-Service nginx.\nginx.exe -s reload
Install NSSM
- Download NSSM from the official website: https://nssm.cc/download.
- Choose the version that matches your system (e.g.,
nssm-2.24.zip
).
- Choose the version that matches your system (e.g.,
- Extract the Zip File:
- Extract it to a folder, such as
C:\Apps\NSSM
.
- Extract it to a folder, such as
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 namenginx
).
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
- Use
.\nginx.exe -t
for testing configuration files—it’s the only “native” command you can safely run alongside NSSM. - Always use
Stop-Service
,Start-Service
, orRestart-Service
for controlling the server. - 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
- Path:
- Ensure this points to the Nginx executable:
C:\nginx\nginx.exe
- Ensure this points to the Nginx executable:
- Startup Directory:
- This should be the Nginx root directory:
C:\nginx
- This should be the Nginx root directory:
- Arguments (optional):
- Specify a custom configuration file if needed:
-c C:\nginx\conf\nginx.conf
- Leave blank if using the default configuration.
- Specify a custom configuration file if needed:
Details Tab
- Display Name:
- Change the service name for clarity, e.g.:
Nginx Web Server
- Change the service name for clarity, e.g.:
- Description:
- Add a brief description of the service, e.g.:
Runs Nginx as a web server on this machine.
- Add a brief description of the service, e.g.:
- Startup Type
- 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.
- This ensures your service starts according to your requirements.
Logon Tab
- 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.
- By default, Nginx runs under the
- Username & Password:
- Enter the credentials if you’re running the service under a specific user account.
Username: myServerUser Password: mySecurePassword123
I/O Tab
- 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
- Standard Output (stdout):
- Redirect Nginx output logs for debugging or monitoring:
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:
- 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.
- When enabled, the current logs (
- Rotate Files:
- Enables periodic rotation of log files without requiring a service restart.
- Useful for managing continuous logs.
- 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.
- 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
anderror.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:
- Save this script as
rotate_logs.ps1
. - Schedule it in Task Scheduler to run daily or weekly.
- 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
.
- Example:
- 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:
- Application Tab:
- Path:
C:\nginx\nginx.exe
- Startup Directory:
C:\nginx
- Arguments:
-c C:\nginx\conf\nginx.conf
(optional)
- Path:
- Details Tab:
- Display Name:
Nginx Web Server
- Description:
Runs Nginx as a web server for this machine.
- Display Name:
- Logon Tab:
- Default:
Local System Account
- If security is a concern, use a specific user account with limited permissions.
- Default:
- 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.
- stdout:
- Shutdown Tab:
- Enable graceful shutdown options:
- Generate Control-C
- Send WM_CLOSE to Windows
- Post WM_QUIT to threads
- Enable graceful shutdown options:
- Exit Actions Tab:
- Restart Action:
Restart application
- Delay Restart:
5000 ms
(5 seconds)
- Restart Action:
- File Rotation Tab:
- Enable rotation for
stdout.log
andstderr.log
with size and/or age restrictions. - Use a PowerShell script (example above) for advanced rotation of Nginx logs.
- Enable rotation for
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)
- 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
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 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
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
- TXT Record Name:
_acme-challenge.<yourdomain.eu
>
This is the name of the TXT record you need to add to your DNS configuration. - TXT Record Value:
cK9yLugh19_kk2u-DGkb4Wvh6sRql1qq7lrjNwAxxxxx
- 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. - TXT Record Value:
iGXBQsTpxKxUZVXIx6dEVDoOKyu5gR_JHDwg4xxxx
The values of the TXT records, will be different each time you issue an SSL.
Why It’s Needed
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
- Log in to Your DNS Management Console:
Access the DNS settings for theyourdomain.eu
domain, typically provided by your domain registrar or hosting provider. - 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
- Name:
- 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. - 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.
- Verify the TXT records with a DNS query tool, like
- 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.
- Continue the Certificate Request :
After adding the TXT records and the TXT records are verified, go back to the terminal whereNew-PACertificate
is running and press Enter to continue, Posh-ACME will verify the records and, if successful, generate the certificate.
Troubleshooting Validation
- 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.
- 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:
- Installed Posh-ACME:
- Ensured the environment is ready for ACME operations.
- Set the ACME Server to Staging:
- Used
Set-PAServer LE_STAGE
to avoid hitting production rate limits during testing.
- Used
- 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
>
- Subject (CN): <
- 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
The DNS-01 challenge is a highly secure and flexible way to validate domain ownership because:
- 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.
- 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.
- It’s the only ACME challenge type that supports issuing wildcard certificates (e.g.,
- 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.
- 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.
- 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.
- 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
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
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
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
, andcert.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
Once you’ve successfully generated your SSL certificates, it’s essential to understand the files provided in the output directory:
File Breakdown
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.
- Description: This is your primary SSL certificate for the domain (e.g.,
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.
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.
- Description: Combines the server certificate (
- **
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.
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.
- Description: A PKCS#12 file that includes the certificate and private key (but not the chain). Typically used for Windows or environments requiring
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.
- Description: Similar to
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.
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
- 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).
- 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
).
- Add OpenSSL to the System Path:
- Open System Properties:
- Press
Windows + R
, typesysdm.cpl
, and press Enter.
- Press
- 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 thePath
. - Click OK to save and close.
- Open System Properties:
- 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
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 toC:\Certs\<yourdomain>\cert.key
fullchain.cer
→ Copy toC:\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
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 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
- Open the
nginx.conf
file located at:C:\nginx\conf\nginx.conf
- 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
15.1 Steps to Test
- Open your browser and navigate to:
http://yourdomain.eu
→ Should redirect tohttps://yourdomain.eu
.https://yourdomain.eu
→ Should load directly without any redirection issues.
15.2 Verify the Redirect
- Use a tool like Redirect Checker to confirm the redirect.
- 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
- Open your Nginx configuration file (e.g.,
nginx.conf
or your site-specific configuration file). - 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
- Load your page in a browser over HTTPS:
https://yourdomain.
eu - 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 ofhttp://
withhttps://
in the HTML response body.sub_filter_once off;
Ensures all occurrences ofhttp://
are replaced, not just the first instance.proxy_set_header Accept-Encoding "";
Disables compression (like gzip) for the response so that thesub_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
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
- 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
- After saving the changes, test the Nginx configuration: cd
- 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.
- Use Online Tools:
- Test your site with online security scanners like:
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:
- Launch Kleopatra:
- Open the Kleopatra application from your start menu.
- Create a New Key Pair:
- Click on
File > New Certificate
orCertificates > New Certificate
.
- Click on
- Select OpenPGP Key Pair:
- Choose the
Create a personal OpenPGP key pair
option and click Next.
- Choose the
- 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.
- Name: Enter your full name (e.g.,
- 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).
- Key Material:
- Click Advanced Settings and select:
- 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.
- 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
.
- After creating the key, right-click on your new certificate and select
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 filesecurity.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 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
orECDHE-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
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:
Ensure the output states that the configuration is valid.cd
C:\nginx, .\nginx.exe -t
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:
- Verify the Certificate: Ensure the certificate and chain files are correct and support OCSP.
- Check Firewall Rules: Ensure outgoing requests to the OCSP responder (usually on port 80) are allowed.
- Review Logs: Enable debug logging in Nginx:
error_log logs/error.log debug;
Check the logs for errors related to OCSP stapling. - Test the Resolver: Confirm that the specified DNS resolvers (e.g., Google DNS) are accessible and working.
Step 19: Add CAA Records in DNS:
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
Adding “Expires headers” to improve caching can further optimize your site for speed. This step can reduce unnecessary HTTP requests on repeat visits.
- Enable HTTP/2:
- If you are not already using HTTP/2, consider enabling it for better performance and security.
- Gzip Compression:
- Improves load time by reducing file sizes.
- Configured to compress text, CSS, JavaScript, and XML responses.
- 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.
- 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:
- Verify Configuration: cd
C:\nginx, .\nginx.exe -t
- Restart Nginx:
Restart-Service nginx
- Test Results:
- Use Http/2 Verify to check for HTTP 2 availability on your server
- Use Pingdom and PageSpeed Insights to validate improvements.
- Check the headers for
Cache-Control
andExpires
.
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
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
- 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.
- Test HTTP redirection:
- Visit http://yourdomain.eu.
- Ensure it automatically redirects to https://yourdomain.eu with no errors.
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:
- Run the following OpenSSL command in PowerShell:
openssl s_client -connect yourdomain.eu:443 -status
- Look for the
OCSP response:
section in the output. If OCSP stapling is working, you’ll see a valid response. - 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
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:
- Confirm that the certificate and key files are properly linked in
nginx.conf
. - 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
- Check if the intermediate certificate is included. If missing, use the
fullchain.cer
file instead ofcert.cer
.
- Confirm that the certificate and key files are properly linked in
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:
- Verify the
root
directive in the HTTPS server block points to the correct directory:root C:/nginx/html;
- Check the file permissions of the website files in
C:/nginx/html
. - Ensure the
index.html
file exists in the specified directory.
- Verify the
5. Changes to nginx.conf
Not Reflected
- Problem: You made changes to
nginx.conf
, but they are not taking effect. - Solution:
- Restart Nginx to reload the configuration:
Restart-Service nginx
- Verify that you are editing the correct configuration file:
C:\nginx\conf\nginx.conf
- Check for syntax errors by running:
cd
C:\nginx, .\nginx.exe -t
- Restart Nginx to reload the configuration:
6. Firewall Blocking HTTPS or HTTP
- Problem: The site is not accessible externally.
- Solution:
- Ensure the necessary firewall rules are added to allow traffic on ports
80
(HTTP) and443
(HTTPS):- Add an inbound rule for Nginx:
Allow program: C:\nginx\nginx.exe Ports: 80, 443
- Add an inbound rule for Nginx:
- Verify external connectivity using a tool like CanYouSeeMe to confirm your server is reachable on the specified ports.
- Ensure the necessary firewall rules are added to allow traffic on ports
7. Certificate Renewal Issues
- Problem: Let’s Encrypt certificates are not renewing automatically.
- Solution:
- Automate certificate renewal with a scheduled task to run Posh-ACME.
- Ensure the DNS TXT record is updated during renewal.
- 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:
- Wazuh for comprehensive monitoring.
- Advanced Nginx documentation for more details: nginx.org documentation.