In today's security-conscious world, enforcing HTTPS on your web applications is no longer optional—it's essential. Whether you're dealing with government reports or routine security audits, failing to enforce HTTPS can trigger warnings that leave you scrambling for a solution.
In this article, I’ll walk you through a recent experience of enforcing HTTPS on a Drupal site hosted on an Nginx server. We’ll go over the steps taken, challenges encountered, and some useful tools for ensuring your site is secure. For privacy, I’ll obfuscate the domain used, but the methods are universally applicable.
Why HTTPS enforcement matters
We were receiving quarterly reports from a government department, highlighting various aspects of our site’s security. One persistent issue was that HTTPS was "not strictly enforced" across several domains, despite having valid SSL certificates installed. This was a red flag—properly enforced HTTPS is crucial for user security, data integrity, and compliance with modern web standards.
Browsers and security tools are increasingly unforgiving when it comes to unencrypted HTTP traffic. Any failure to enforce HTTPS can lead to warnings, or worse, browser blocks. Therefore, we needed a robust solution to ensure that all traffic to our site was securely routed through HTTPS.
Initial setup: basic Nginx configuration
Our site is powered by Drupal, running on Nginx. Initially, our Nginx configuration looked like this for HTTP traffic:
server {
listen 80;
server_name example.com;
return 301 http://www.example.com$request_uri;
}
This block was redirecting all traffic from http://example.com to http://www.example.com, which was functional but not secure. The goal was to redirect traffic to https://www.example.com instead.
To handle HTTPS traffic, we had another server block:
server {
listen 443 ssl;
listen [::]:443 ssl;
ssl_certificate /etc/ssl/certs/ExampleBundle.crt;
ssl_certificate_key /etc/ssl/private/ExampleWildcard.key;
server_name example.com;
return 301 https://www.example.com$request_uri;
}
However, even with this configuration in place, we were still receiving warnings about HTTPS not being strictly enforced.
Problem: HTTP traffic not redirected to HTTPS
The key issue was that HTTP traffic was still being redirected to http://www.example.com, which meant that users were being directed to an insecure version of the site before being redirected to HTTPS.
We needed to update the configuration to ensure that any HTTP request was immediately redirected to HTTPS.
Solution: updating the Nginx configuration
We modified the Nginx configuration to enforce HTTPS by updating the HTTP redirect:
server {
listen 80;
listen [::]:80;
server_name example.com;
return 301 https://www.example.com$request_uri;
}
This change ensured that any request made to http://example.com would be redirected directly to https://www.example.com, enforcing secure connections from the start.
Additional security headers
While enforcing HTTPS is critical, there are a few additional headers that can further enhance your site’s security. We added the following headers to our Nginx configuration:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
- Strict-Transport-Security (HSTS): This header tells browsers to always use HTTPS, even if a user types in http://. Setting this header ensures that once users visit your site over HTTPS, their browser will automatically use HTTPS for future requests.
- X-Content-Type-Options: nosniff: This header prevents the browser from MIME-type sniffing. In essence, it makes sure that files like scripts or stylesheets are not mistakenly executed as something else.
- X-Frame-Options: DENY: This header prevents your site from being embedded in an <iframe>, protecting against clickjacking attacks.
Testing the changes
After making these changes, we needed to test the configuration to ensure that HTTPS was being enforced correctly. One of the easiest ways to test this is by using the curl command. Below are the test results we obtained:
Test 1: Redirect from http://example.com
curl -I http://example.com
Response:
HTTP/1.1 301 Moved Permanently
Location: https://www.example.com/
This shows that any request to http://example.com was now being redirected to the secure https://www.example.com, as intended.
Test 2: Redirect from http://www.example.com
curl -I http://www.example.com
Response:
HTTP/1.1 301 Moved Permanently
Location: https://www.example.com/
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
This confirmed that traffic to http://www.example.com was also being redirected to the HTTPS version, with the correct security headers in place.
Test 3: Accessing https://www.example.com
curl -I https://www.example.com
Response:
HTTP/1.1 200 OK
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
This confirmed that the site was correctly serving HTTPS with all the security headers enabled.
Lessons learned
1. Ensure your redirects use HTTPS
Initially, our redirects pointed to HTTP versions of the site, which defeated the purpose of enforcing HTTPS. Always ensure that your redirects use HTTPS right from the start.
2. Test your changes thoroughly
It’s easy to think everything is working after making configuration changes, but testing is crucial. Using tools like curl helps you catch issues like incorrect redirects before they become bigger problems.
3. Leverage security headers
While HTTPS is important, adding headers like Strict-Transport-Security and X-Content-Type-Options provides additional layers of protection against common web vulnerabilities.
The wrap
Enforcing HTTPS is a necessary step in securing your web applications, and getting it right can be challenging. With proper configuration and testing, you can ensure your site is protected against a variety of attacks and fully compliant with security best practices.
If you’re managing a Drupal site or any other CMS on Nginx, I hope this guide helps you navigate some of the common challenges and implement HTTPS in a way that’s both effective and secure.
The files
Below are two versions of the Nginx configuration file. The first is before any changes were made, and the second is post changes with the proper HTTPS enforcement and security headers.
Nginx Configuration Before Any Changes
server {
listen 80;
server_name example.com;
return 301 http://www.example.com$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
ssl_certificate /etc/ssl/certs/ExampleBundle.crt;
ssl_certificate_key /etc/ssl/private/ExampleWildcard.key;
server_name example.com;
return 301 https://www.example.com$request_uri;
}
server {
listen 80;
listen [::]:80;
listen 443 ssl;
ssl_certificate /etc/ssl/certs/ExampleBundle.crt;
ssl_certificate_key /etc/ssl/private/ExampleWildcard.key;
root /var/www/html/example;
index index.php index.html index.htm;
server_name www.example.com;
location / {
try_files $uri /index.php?$query_string;
}
location @rewrite {
rewrite ^/(.*)$ /index.php?q=$1;
}
location ~ [^/]\.php(/|$) {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php7.4-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ ^/sites/.*/files/styles/ {
try_files $uri @rewrite;
}
location ~ ^(/[a-z\-]+)?/system/files/ {
try_files $uri /index.php?$query_string;
}
location ~* "^/Archived-Reports/Example Projects/(.*)" {
return 301 /sites/default/files/products/$1;
}
}
Nginx Configuration Post Changes
# Redirect all HTTP traffic from example.com to HTTPS
server {
listen 80;
listen [::]:80;
server_name example.com;
return 301 https://www.example.com$request_uri;
}
# Redirect HTTPS traffic from example.com to www.example.com
server {
listen 443 ssl;
listen [::]:443 ssl;
ssl_certificate /etc/ssl/certs/ExampleBundle.crt;
ssl_certificate_key /etc/ssl/private/ExampleWildcard.key;
server_name example.com;
return 301 https://www.example.com$request_uri;
}
# Main block for www.example.com with HTTPS enforcement and security headers
server {
listen 80;
listen [::]:80;
listen 443 ssl;
listen [::]:443 ssl;
ssl_certificate /etc/ssl/certs/ExampleBundle.crt;
ssl_certificate_key /etc/ssl/private/ExampleWildcard.key;
server_name www.example.com;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
root /var/www/html/example;
index index.php index.html index.htm;
# Redirect all HTTP traffic to HTTPS
if ($scheme = http) {
return 301 https://$server_name$request_uri;
}
location / {
try_files $uri /index.php?$query_string;
}
location @rewrite {
rewrite ^/(.*)$ /index.php?q=$1;
}
location ~ [^/]\.php(/|$) {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ ^/sites/.*/files/styles/ {
try_files $uri @rewrite;
}
location ~ ^(/[a-z\-]+)?/system/files/ {
try_files $uri /index.php?$query_string;
}
location ~* "^/Archived-Reports/Example Projects/(.*)" {
return 301 /sites/default/files/products/$1;
}
}