A 301 redirect from HTTP to HTTPS is good. HSTS is better. Here's the difference: a redirect still requires one initial HTTP request before the browser is told to switch to HTTPS. In that brief window, an attacker on the same network can intercept the unencrypted request — a classic SSL stripping attack. HSTS eliminates that window entirely by telling the browser to never attempt an HTTP connection to your domain in the first place.
Enable HSTS on Windows IIS
Windows Server 2019+
IIS 10
Windows Server 2019 and IIS 10 include native HSTS support. On older versions, add HSTS as a custom response header.
Method 1: Native HSTS (Windows Server 2019 / IIS 10+)
Import-Module WebAdministration
# Enable HSTS with a 1-year max-age, includeSubDomains, and preload
Set-WebConfigurationProperty -PSPath "IIS:\Sites\Default Web Site" `
-Filter "system.webServer/hsts" -Name "enabled" -Value $true
Set-WebConfigurationProperty -PSPath "IIS:\Sites\Default Web Site" `
-Filter "system.webServer/hsts" -Name "max-age" -Value 31536000
Set-WebConfigurationProperty -PSPath "IIS:\Sites\Default Web Site" `
-Filter "system.webServer/hsts" -Name "includeSubDomains" -Value $true
Set-WebConfigurationProperty -PSPath "IIS:\Sites\Default Web Site" `
-Filter "system.webServer/hsts" -Name "preload" -Value $true
# Verify the settings:
Get-WebConfigurationProperty -PSPath "IIS:\Sites\Default Web Site" `
-Filter "system.webServer/hsts" -Name "*"
Method 2: Custom Response Header (IIS 8.5 and older)
Add the HSTS header via web.config:
<configuration>
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Strict-Transport-Security"
value="max-age=31536000; includeSubDomains; preload" />
</customHeaders>
</httpProtocol>
</system.webServer>
</configuration>
HTTPS only: IIS will only send the HSTS header on HTTPS responses. Make sure your HTTP→HTTPS redirect is in place before enabling HSTS, otherwise the header is never delivered to the browser.
macOS (Homebrew Apache / Nginx)
On macOS with Homebrew Apache or Nginx, use the identical directives shown in the Linux sections above. The syntax and values are the same — only the config file paths differ:
# Homebrew Apache — add to VirtualHost *:443 block in:
# /opt/homebrew/etc/httpd/extra/httpd-ssl.conf
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
# Homebrew Nginx — add inside server { listen 443 ssl; } block in:
# /opt/homebrew/etc/nginx/nginx.conf
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# Reload after changes:
brew services restart httpd # Apache
brew services restart nginx # Nginx