Using Self-Signed S/MIME Certificates in Thunderbird

This guide describes the process for using self-signed S/MIME certificates with Mozilla Thunderbird on Windows 10. If you need an S/MIME certificate, please follow the guide to Create Self-Signed S/MIME Certificates. Certificates allow you to both send digitally signed messages and receive encrypted messages as part of public-key cryptography (also known as asymmetric cryptography). By creating and self-signing your own certificates, you avoid paying recurring fees but you lose the inherent trust that comes with a certificate generated by a well-known certificate company. After having written similar guides for iOS / iPadOS and Outlook, in my opinion, Thunderbird is probably the easiest to establish trust and set up your own certificates.

Part 1 – Trusting Self-Signed S/MIME Personal Certificates

Step 1 – Account Settings

From the Thunderbird menu (the hamburger icon button or collapsed menu icon button), select Account Settings.

Thunderbird – Account Settings Menu
Thunderbird – Account Settings Menu

Step 2 – End-To-End Encryption

The Account Settings screen is displayed. Select End-To-End Encryption from the account settings menu. Click the Manage S/MIME Certificates button.

Account Settings – End-To-End Encryption
Account Settings – End-To-End Encryption

Step 3 – Establish Personal Certificate Trust

The Certificate Manager window is displayed. From the available tabs, select Your Certificates followed by the Import… button. The Certificate File to Import window is displayed. Find the .p12 file containing the appropriate self-signed certificate for this e-mail account and click the Open button.

Certificate Manager
Certificate Manager

The Password Required prompt is displayed. Enter the password set when the .p12 package was created and then click the Sign in button.

PKCS12 Package Password
PKCS12 Package Password

The personal certificate is now displayed in the Your Certificates tab of the Certificate Manager.

Certificate Manager – Personal Certificates
Certificate Manager – Personal Certificates

Step 4 – Establish Certificate Authority Trust

Now the certificate authority certificate needs to be added to the Certificate Manager so that the personal certificate can be trusted. Select the Authorities tab and click the Import… button.

Certificate Authorities – Authorities
Certificate Authorities – Authorities

The Select File containing CA certificate(s) to import window is displayed. Find the .crt file containing the appropriate certificate authority certificate used to create the personal certificate and click the Open button. If the guide Create Self-Signed S/MIME Certificates was followed, then the file is named ca.crt.

Added Certificate Authority Certificate
Added Certificate Authority Certificate

The Downloading Certificate prompt is displayed. Confirm the displayed information and CA certificate are correct. The Trust this CA to identify email users checkbox should be checked. Click the OK button when complete.

Downloading Certificate – Authority Certificate
Downloading Certificate – Authority Certificate

The certificate authority certificate is now displayed in the Certificate Manager window on the Authorities tab. In this example, the certificate authority certificate was established using the Organization Name as TEST COMPANY and the Common Name as TEST COMPANY CERTIFICATE AUTHORITY. Click the OK button to close the Certificate Manager window.

Added Certificate Authority Certificate
Added Certificate Authority Certificate

Step 5 – S/MIME Settings for Digital Signing and Encryption

Now that trust has been established for the certificate authority certificate as well as the personal certificate, the S/MIME section of the End-To-End Encryption settings screen can be completed.

Account Settings – End-To-End Encryption (Completed)
Account Settings – End-To-End Encryption (Completed)

In the Personal certificate for digital signing field, click the Select… button next to the field. The Select Certificate prompt is displayed. Select the appropriate certificate from the drop-down list and confirm the displayed details. Click the OK button.

Digital Signature Certificate
Digital Signature Certificate

A prompt may be displayed asking “You should also specify a certificate for other people to use when they send you encrypted messages. Do you want to configure an encryption certificate now?” Click the Yes button. The Personal certificate for encryption field is populated with the same certificate specified for the digital signature.

Configure Encryption Certificate
Configure Encryption Certificate

If the question prompt was not displayed, Thunderbird returns to the End-To-End Encryption settings screen. If the Personal certificate for encryption is not populated in the S/MIME section, then click the Select… button next to the Personal certificate for encryption field. The Select Certificate prompt is displayed. Select the appropriate certificate from the drop-down list and confirm the displayed details. Click the OK button. I can’t think of a reason why the digital signature and encryption fields would use different certificates, but I’m sure someone has an edge case where the distinction is needed. Generally, I would expect both fields to reference the same personal certificate.

Encryption Certificate
Encryption Certificate

Close the Account Settings screen.

Part 2 – Sending a Digitally Signed and Encrypted Message

At this point, trust has been established for both the certificate authority certificate and the personal certificate. The personal certificate has been established for use in digital signatures as well as encryption and Thunderbird is ready to send a digitally signed message.

Step 1 – Draft New Message

Click the Write button to draft a new message. A new message window opens.

Step 2 – Set Security Options

In the Options menu for the message, select Digitally Sign This Message.

Message Options
Message Options

I was also able to select Require Encryption because the sender and recipient are the same e-mail address in this example (I sent an e-mail to myself). Since the sender and recipient are the same, Thunderbird already has a trusted recipient certificate. If Require Encryption is selected and a recipient certificate isn’t available, then Thunderbird displays the error “Sending of the message failed. You specified encryption for this message, but the application failed to find an encryption certificate for <e-mail address>.”

Encryption Error
Encryption Error

The Digitally Sign This Message and Require Encryption options may also be set by clicking the Security button. View Security Info will display the recipient certificate, if available.

Message Security Options
Message Security Options

Step 3 – Send Message

Complete drafting the message and click the Send button. A Message Security prompt is displayed. Confirm the displayed information is correct and click the OK button to send the message.

Send Message – Security Warning
Send Message – Security Warning

Result

The recipient receives a digitally signed message using the sender’s self-signed certificate. In this example, since the sender and recipient are the same, the recipient also received an encrypted message.

Digitally Signed and Encrypted Message
Digitally Signed and Encrypted Message

Further Reading

Using Self-Signed S/MIME Certificates in Outlook

If you followed my guide to create self-signed S/MIME certificates, then you will have the necessary files to begin digitally signing and receiving encrypted e-mail. As long as the e-mail client supports S/MIME, which Outlook does support, then you can create and use your own certificates for any e-mail address including custom domains, Gmail, iCloud, or even AOL. This guide describes the process for using self-signed S/MIME certificates with Microsoft Outlook 2019 found in the Microsoft Office Professional Plus 2019 suite running on Windows 10.

Part 1 – Trusting the Self-Signed Certificate Authority

Since we’re using self-signed certificates, Windows and Outlook will not automatically recognize your personal certificate authority. If a certificate authority is not trusted, then any certificates issued by that certificate authority are not trusted and they are considered invalid by Outlook. If you try to send a digitally signed e-mail using your personal certificate before the certificate authority is trusted, then Outlook displays the message “Microsoft Outlook cannot sign or encrypt this message because your certificate is not valid.”

Invalid Certificate
Invalid Certificate

Step 1 – Install Certificate

Find the certificate authority certificate. If you followed my guide, the file is named ca.crt. Double-click on the file in File Explorer to open it. The Certificate Information screen is displayed. Confirm that the Issue to, Issued by, and Valid from/to dates match the expected values.

Click the Install Certificate… button.

Install Self-Signed Certificate Authority Certificate
Install Self-Signed Certificate Authority Certificate

Step 2 – Select Store Location

The Certificate Import Wizard screen is displayed. Choose Current User and click the Next button.

Certificate Import Wizard – Store Location
Certificate Import Wizard – Store Location

Step 3 – Select Certificate Store

The Certificate Store screen is displayed. Select Place all certificates in the following store and click the Browse… button.

Certificate Import Wizard – Store Selection
Certificate Import Wizard – Store Selection

The Select Certificate Store screen is displayed. Select Trusted Root Certification Authorities and click the OK button.

Certificate Import Wizard – Trusted Root Certification Authorities
Certificate Import Wizard – Trusted Root Certification Authorities

Confirm the details on the Select Certificate Store screen and click the Next button.

Step 4 – Complete the Certificate Import Wizard

The Completing the Certificate Import Wizard screen is displayed. Confirm the displayed details and click the Finish button.

Certificate Import Wizard – Completion
Certificate Import Wizard – Completion

Step 5 – Security Warning

A Security Warning screen is displayed requesting confirmation that the certificate should be installed. Confirm the displayed details and click the Yes button.

Security Warning
Security Warning

The Import Successful dialog box is displayed.

Import Successful
Import Successful

Close the Certificate Information screen by clicking the OK button.

Step 6 – Confirm Trusted Certification Authorities in CertMgr

From the Windows start menu, run certmgr (Manager user certificates). Under Current User, expand Trusted Root Certification Authorities and click Certificates. Review the list of certificates to confirm your certificate authority is in the store. Close the application.

CertMgr – Trusted Root Certification Authorities
CertMgr – Trusted Root Certification Authorities

Part 2 – Installing the Self-Signed S/MIME Certificate in Outlook

With the certificate authority certificate in the Windows trust store, we can now add our self-signed S/MIME certificate to Outlook.

Step 1 – Open Trust Center

Open Outlook and select File and then Options. The Outlook Options screen is displayed. Select Trust Center.

Outlook Options – Trust Center
Outlook Options – Trust Center

Step 2 – Open Email Security

Click the Trust Center Settings… button. The Trust Center screen is displayed. Select Email Security.

Trust Center – Email Security
Trust Center – Email Security

Step 3 – Import Self-Signed S/MIME Certificate

Click the Import/Export… button. The Import/Export Digital ID screen is displayed. In the Import existing Digital ID from a file section, click the Browse… button. Find the relevant PKCS12 file. If you followed my guide, the file is named smime_test_user.p12. Enter the password for the package.

Trust Center – Import/Export Digital ID
Trust Center – Import/Export Digital ID

Click the OK button.

Step 4 – Import Certificate

The Importing a new private exchange key dialog box is displayed. Click the OK button.

Importing a New Private Exchange Key
Importing a New Private Exchange Key

Step 5 – Change Security Settings

Returning to the Trust Center screen, click the Settings… button. The Change Security Settings screen is displayed. Confirm the displayed information is correct. Change the Security Settings Name value to a unique name for the certificate.

Trust Center – Change Security Settings
Trust Center – Change Security Settings

If the Signing Certificate or Encryption Certificate are blank, then click either Choose… button. The Windows Security Confirm Certificate dialog box is displayed. Click the OK button.

Windows Security – Confirm Certificate
Windows Security – Confirm Certificate

Confirm the information on the Change Security Settings dialog box are correct and click the OK button.

Step 6 – Confirm Default Settings

On the Trust Center Email Security settings screen, confirm the Default Setting references the security settings name created in the prior step. Click the OK button to close the Trust Center screen. On the Outlook Options screen, click the OK button to close the screen.

Trust Center – Email Security Settings Completed
Trust Center – Email Security Settings Completed

Part 3 – Sending a Digitally Signed E-mail

Finally, we can send a digitally signed e-mail in Outlook using a self-signed S/MIME certificate issued by a personal certificate authority. As a reminder, this does not allow you to send encrypted e-mails since public-key cryptography requires the sender to have the public key for the recipient (we only have the sender keys). However, this does allow you to send a digitally signed e-mail to a recipient. Since the digital signature contains your public key, the recipient can than respond with an encrypted e-mail after establishing trust in their e-mail client.

Step 1 – Draft and Sign E-mail

Create a new e-mail using the e-mail address associated with the S/MIME certificate. From the Options ribbon, click Sign.

Outlook – Digitally Signed E-mail
Outlook – Digitally Signed E-mail

Step 2 – Send E-mail

Complete the e-mail and click the Send button. A Windows Security dialog box is displayed requesting access to the private key. Click the Allow button.

Outlook – Credential Required
Outlook – Credential Required

Result

When the e-mail is received, the recipient’s e-mail client displays an indicator that the e-mail is digitally signed. Outlook’s indicator for digitally signed e-mail is a small ribbon. The recipient may then need to trust the certificate (public key) contained in your digital signature in order to respond with an encrypted message.

Outlook – Digital Signature Indicator
Outlook – Digital Signature Indicator

If you send a digitally signed e-mail from the e-mail address back to itself, you can respond to that e-mail with an encrypted message. Open the e-mail and click Reply. From the Options ribbon, click Encrypt. Add a response to the message body and click the Send button. When the response is received, Outlook displays a lock icon to indicate that the e-mail is encrypted. Outlook automatically handles the decryption when the e-mail is opened.

Outlook – Encryption Indicator
Outlook – Encryption Indicator

Part 4 – Establishing Recipient Trust

When a recipient receives a digitally signed e-mail where the sender used a self-signed S/MIME certificate and a personal certificate authority, the message is flagged by Outlook with the message “There are problems with the signature. Click the signature button for details.” The signature button is the yellow triangle with an exclamation point. As the sender, we added trust in Part 1 of this guide to the sending machine, however, the recipient machine does not recognize the certificate authority so the digital signature certificate is not trusted and flagged as invalid.

Outlook – Digital Signature is Invalid Warning
Outlook – Digital Signature is Invalid Warning

Step 1 – Invalid Digital Signature

Click the yellow triangle with an exclamation point icon. The Digital Signature: Invalid dialog box is displayed.

Outlook – Digital Signature Invalid
Outlook – Digital Signature Invalid

Step 2 – Message Security Properties (Invalid)

Click the Details… button. The Message Security Properties screen is displayed. There are many intimidating red circles with exclamation points.

Outlook – Message Security Properties (Invalid)
Outlook – Message Security Properties (Invalid)

Step 3 – Trust Certificate Authority

If available, click the Trust Certificate Authority… button. The Trust Certificate Authority screen is displayed. Click the Trust button. The Trust Certificate Authority screen closes. If the button is not active, proceed to the next step.

Outlook – Trust Certificate Authority
Outlook – Trust Certificate Authority

Step 4 – View Certificate and Edit Trust

Returning to the Message Security Properties screen, click the Edit Trust… button. The View Certificate screen is displayed. In the Edit Trust section, select Explicitly Trust this Certificate. Click the OK button.

Outlook – View Certificate
Outlook – View Certificate

Step 5 – Message Security Properties (Valid)

Returning to the Message Security Properties screen again, we find all the red circles have been replaced with green check marks. Click the Close button.

Outlook – Message Security Properties (Valid)
Outlook – Message Security Properties (Valid)

Step 6 – Valid and Trusted Digital Signature

The Digital Signature: Invalid dialog box is now the Digital Signature: Valid dialog box. Click the Close button. The digital signature is now trusted and flagged as valid.

Outlook – Digital Signature Valid
Outlook – Digital Signature Valid

Further Reading

Hardening WordPress Security Using .htaccess

The default .htaccess file created by WordPress during the installation process on Apache contains only the basic directives needed for WordPress to function. Additional directives may be included in .htaccess to further secure and harden WordPress from common attacks. Many of the below suggestions are not specific to WordPress so they may be used to increase the security posture of any site served by Apache. Other directives are also included below to reduce bandwidth consumption and improve site performance through HTTP compression and browser caching.

The following examples and suggestions assume a single WordPress installation in the document root (not installed in a sub-directory). It also assumes the typical user with a modern browser. If you have a large userbase of early generation browsers, then please adjust accordingly. Remember, defense in depth, so combine these methods with others to create a balance of security and usability.

Prevent Directory Listings

The first directive in the file disables directory listings. If a user attempts to browse a directory, Apache won’t serve a listing of all subdirectories or files at that location. This limits exposure of the underlying file system structure and content.

Options All -Indexes

Hide Apache Version

The next directive instructs Apache to not display its version information on server generated error pages, listings, etc. If a bad actor is scanning for a version of Apache with a known vulnerability, this makes it slightly more difficult to fingerprint.

ServerSignature Off

Limit Allowed HTTP Request Methods

Allowed request methods are limited to POST, GET, HEAD, and OPTIONS. If the site is using other methods, then adjust the list accordingly. By leaving unused request methods open, it increases the attack surface and exposes the site to potential attacks. Bad actors attempt to exploit vulnerabilities in the server configuration by sending crafted requests or malformed request methods.

<LimitExcept POST GET HEAD OPTIONS>
  Order allow,deny
  Require all denied
</LimitExcept>

Block IP Addresses and Ranges

Known IP addresses of spammers or other bad actors are blocked from accessing the site in the following directive. If you monitor the server log files for any period of time, you will find certain IP addresses are constantly scanning for vulnerabilities. Specific addresses or ranges can be blocked by adjusting the RewriteCond lines. Replace the addresses in the example with the appropriate address numbers or ranges. If a user with a listed IP address attempts to connect to the site, the request fails and no further directives are evaluated. This directive can quickly grow too large to realistically maintain. Consider leveraging stronger firewall solutions for easier and automated blocking.

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteCond %{REMOTE_ADDR} ^10\.0\.0\.1 [OR]
  RewriteCond %{REMOTE_ADDR} ^10\.32\.64\.250
  RewriteRule ^ - [NC,F,L]
</IfModule>

Block Referrer Links

If the site receives referral traffic from either undesirable sites or entire generic top-level domains, this directive prevents incoming traffic from those sites when the supplied referrer information matches the RewriteCond lines. While this does not prevent other sites from simply including a link on their site or copying content, it does block incoming traffic from those sites. In the example, traffic from all .cc, .eu. and .ru top-level domains are blocked as well as traffic from example1.com and example2.com. When the referrer data matches either condition, the request fails and no further directives are evaluated. Adjust the conditions to meet the specific needs of your site as this directive is rather restrictive.

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteCond %{HTTP_REFERER} ^http(?:s)?://(?:.*)?\.(?:cc|eu|ru)(?:/.*)?$ [NC,OR]
  RewriteCond %{HTTP_REFERER} ^http(?:s)?://(?:.*\.)?(?:example1.com|example2.com)(?:/.*)?$ [NC]
  RewriteRule ^ - [NC,F,L]
</IfModule>

Block HTTP/1.0 Requests

The following directive blocks any user attempting to access the site using HTTP/1.0. I chose to block this protocol because it is outdated and I assume any connection attempt using this protocol is likely not coming from a typical user. If a user attempts to connect to the site using HTTP/1.0, the request fails and no further directives are evaluated.

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteCond %{SERVER_PROTOCOL} ^HTTP/1\.0$
  RewriteRule ^ - [NC,F,L]
</IfModule>

Block Requests with Empty HTTP_USER_AGENT String

This directive assumes that all typical users have an HTTP_USER_AGENT string which signals to the server the product name / browser and version used to access the site. If the user-agent string is blank or whitespace or dashes, then the request fails and no further directives are evaluated.

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteCond %{HTTP_USER_AGENT} ^(?:\s|-)*$
  RewriteRule ^ - [NC,F,L]
</IfModule>

Block Access to WordPress Configuration Files and XML-RPC

If a user attempts to access a file listed in the directive, the request fails. The files listed here are common configuration files or log files for WordPress which should not be exposed to general users for security reasons. The XML-RPC endpoint is commonly used by bad actors for DDoS and brute force attacks since it provides programmatic remote access to a site.

<FilesMatch "wp-config\.php|xmlrpc\.php|error_log|readme\.html|license\.txt|wp-config-sample\.php">
  Order allow,deny
  Require all denied
</FilesMatch>

Restrict WordPress Login to Known IP Addresses

This directive restricts access to wp-login to specific IP addresses or ranges. If an installation only has a handful of registered users, this directive limits login attempts to those known user IP addresses. This reduces a bad actor’s ability to brute force login attempts. Ensure the allowed IP list includes the site’s public IP address otherwise WordPress will indicate an issue in Site Health because it is unable to perform loopback requests. Replace the addresses in the example with the appropriate address numbers or ranges. Please keep in mind that this is very restrictive and you will not be able to login if you are accessing the site from a different IP address, e.g. mobile.

<FilesMatch "wp-login\.php">
  Order deny,allow
  deny from all
  allow from 172.16.0.1
  allow from 172.30.254.1
</FilesMatch>

Block Hotlinking

The following directive prevents hotlinking of images and common files. Hotlinking occurs when another site links directly to an image/file on your site instead of hosting the image/file itself. If the other site has high traffic, it will consume your site’s bandwidth and potentially degrade server performance. It may also have a cost impact if your bandwidth threshold is exceeded. Adjust the RewriteCond to your domain(s). Again, if the request is not coming from a specified domain, then the request fails and no further directives are evaluated.

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteCond %{HTTP_REFERER} !^$
  RewriteCond %{HTTP_REFERER} !^http(?:s)?://(?:.*\.)?dalesandro\.net(?:/.*)?$ [NC]
  RewriteRule \.(?:jpe?g|gif|png|svg|webp|zip|rar|pdf)$ - [NC,F,L]
</IfModule>

Block Direct Access to /wp-includes

This section restricts direct access to the wp-includes directory which contains core WordPress code and scripts. Public access to this directory is not required or intended for a WordPress site to function so direct access should be restricted.

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /
  RewriteRule ^wp-admin/includes/ - [NC,F,L]
  RewriteRule !^wp-includes/ - [S=3]
  RewriteRule ^wp-includes/[^/]+\.php$ - [NC,F,L]
  RewriteRule ^wp-includes/js/tinymce/langs/.+\.php - [NC,F,L]
  RewriteRule ^wp-includes/theme-compat/ - [NC,F,L]
</IfModule>

Block WordPress Author Scans

Author scans are performed to enumerate usernames for WordPress based sites. Any usernames identified through these scans can be used in brute force attempts to access the admin section of the site. If you browse to a WordPress site and include /author=1 (or 2, 3, 4, etc.), WordPress will return author details for that particular ID. This directive blocks these requests.

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /
  RewriteCond %{QUERY_STRING} (author=\d+) [NC]
  RewriteRule ^ - [NC,F,L]
</IfModule>

Return 410 Gone Response for Deleted Posts/Pages

While not security focused, this is an example of how to handle removed posts/pages. The “G” flag in the RewriteRule returns a 410 Gone response status code which informs browsers and bots that the resource has been intentionally removed.


<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteCond %{REQUEST_URI} ^/post-permalink(?:/.*)?$ [OR,NC]
  RewriteCond %{REQUEST_URI} ^/2021/02/28/another-post-permalink(?:/.*)?$ [OR,NC]
  RewriteCond %{REQUEST_URI} ^/yet-another-post-permalink(?:/.*)?$ [NC]
  RewriteRule ^ - [NC,G,L]
</IfModule>

Permanent Redirects (301) for Renamed Permalinks

Again, not a security focused example, but this example demonstrates how to handle changed permalinks. If the permalink for a specific post/page has changed, these directives return a 301 Moved Permanently response status code. This provides browsers/bots with the new resource location for the redirect and avoids a 404 Not Found response status code while informing bots to update its index. Adjust the RewriteCond to your domain(s).

RedirectMatch 301 ^/post-permalink(?:/.*)?$ https://www.dalesandro.net/updated-post-permalink/
RedirectMatch 301 ^/another-post-permalink(?:/.*)?$ https://www.dalesandro.net/replacement-permalink/

RedirectMatch may also be used for more significant and sitewide changes to the permalink structure such as changing from the “Day and name” permalink structure which includes the year, month, and day followed by the post name, e.g. /2013/07/06/sample-post/, to the “Post name” permalink structure which only includes the post name, e.g. /sample-post/. Assuming the site has been previously indexed under the old permalink structure, the search results will return the old “Day and name” URLs which will generate 404 Not Found response status codes.

This directive returns the more appropriate response code of 301 (moved permanently) and redirects users to the appropriate URL in the new permalink structure (assuming a change from “Day and name” to “Post name”).

RedirectMatch 301 ^/([0-9]{4})/([0-9]{2})/([0-9]{2})/(?:.*)$ https://www.dalesandro.net/$4

Reduce Automated WordPress Comment Spam

WordPress sites that allow comment submissions will receive comment spam. These comments are usually submitted through automated bots. In many cases, these bots don’t actually submit the comment through the form displayed on a post/page. Instead, the comment is submitted programmatically and directly through wp-comments-post.php. This directive assumes that any POST submissions to wp-comments-post.php are spam where the referrer is not your own domain or the user-agent string is empty. When these conditions are met, then the request fails and no further directives are evaluated. Adjust the RewriteCond to your domain(s).

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteCond %{REQUEST_METHOD} POST [NC]
  RewriteCond %{REQUEST_URI} wp-comments-post\.php [NC]
  RewriteCond %{HTTP_REFERER} !^http(s)?://(.*\.)?dalesandro\.net(/.*)?$ [OR,NC]
  RewriteCond %{HTTP_USER_AGENT} ^(?:\s|-)*$
  RewriteRule ^ - [NC,F,L]
</IfModule>

Add Secure Headers

The following section adds secure headers to HTTP responses. In modern browsers, these headers help prevent some vulnerabilities such a cross-site scripting and clickjacking. These should be adjusted for your particular site. For more information, review OWASP Secure Headers Project.

<IfModule mod_headers.c>
  Header set X-XSS-Protection "1; mode=block"
  Header set X-Frame-Options "DENY"
  Header set X-Content-Type-Options nosniff
  Header set Referrer-Policy "same-origin"
</IfModule>

Add HTTP Compression

With the exception of already compressed file types, e.g. image files, zip/rar, pdf, the following directive instructs Apache to compress any other served files. While this isn’t security related, it reduces bandwidth consumption and improves performance.

<IfModule mod_deflate.c>
  SetOutputFilter DEFLATE
  <IfModule mod_setenvif.c>
    SetEnvIfNoCase Request_URI \.(?:jpe?g|gif|png|svg|webp|zip|rar|pdf)$ no-gzip dont-vary
  </IfModule>
</IfModule>

Add Browser Caching Directives

In addition to HTTP compression above, the following directive instructs browser how long to cache certain file types. Obviously, this will reduce bandwidth consumption and improve performance since browsers won’t request the same file over and over with each post/page view. These file types are assumed to be mostly static and remain unchanged for long periods of time. The cache time should be adjusted to meet your own requirements and file rules should be added/removed as needed. These directives require mod_expires which can be enabled in httpd.conf. For instructions on how to load mod_expires, read Enable Browser Caching Directives in Amazon Lightsail Apache Server.

<IfModule mod_expires.c>
  ExpiresActive On
  ExpiresDefault "access plus 1 month"
  ExpiresByType text/cache-manifest "access plus 0 seconds"
  ExpiresByType text/html "access plus 0 seconds"
  ExpiresByType text/xml "access plus 0 seconds"
  ExpiresByType application/xml "access plus 0 seconds"
  ExpiresByType application/json "access plus 0 seconds"
  ExpiresByType application/rss+xml "access plus 1 hour"
  ExpiresByType application/atom+xml "access plus 1 hour"
  ExpiresByType image/x-icon "access plus 1 year"
  ExpiresByType image/gif "access plus 1 year"
  ExpiresByType image/png "access plus 1 year"
  ExpiresByType image/jpg "access plus 1 year"
  ExpiresByType image/jpeg "access plus 1 year"
  ExpiresByType image/webp "access plus 1 year"
  ExpiresByType image/svg+xml "access plus 1 year"
  ExpiresByType image/vnd.microsoft.icon "access plus 1 year"
  ExpiresByType video/ogg "access plus 1 year"
  ExpiresByType audio/ogg "access plus 1 year"
  ExpiresByType video/mp4 "access plus 1 year"
  ExpiresByType video/webm "access plus 1 year"
  ExpiresByType video/mpeg "access plus 1 year"
  ExpiresByType text/x-component "access plus 1 year"
  ExpiresByType font/ttf "access plus 1 year"
  ExpiresByType font/otf "access plus 1 year"
  ExpiresByType font/woff "access plus 1 year"
  ExpiresByType font/woff2 "access plus 1 year"
  ExpiresByType application/font-woff "access plus 1 year"
  ExpiresByType application/x-font-ttf "access plus 1 year"
  ExpiresByType font/opentype "access plus 1 year"
  ExpiresByType application/vnd.ms-fontobject "access plus 1 year"
  ExpiresByType text/css "access plus 1 month"
  ExpiresByType text/javascript "access plus 1 month"
  ExpiresByType text/x-javascript "access plus 1 month"
  ExpiresByType application/javascript "access plus 1 month"
  ExpiresByType application/x-javascript "access plus 1 month"
  ExpiresByType application/pdf "access plus 1 month"
  ExpiresByType application/zip "access plus 1 month"
  <IfModule mod_headers.c>
    Header append Cache-Control "public"
  </IfModule>
</IfModule>

Source Code

Finally, this is the complete .htaccess file containing all of the directives reviewed above.

Options All -Indexes

ServerSignature Off

<LimitExcept POST GET HEAD OPTIONS>
  Order allow,deny
  Require all denied
</LimitExcept>

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteCond %{REMOTE_ADDR} ^10\.0\.0\.1 [OR]
  RewriteCond %{REMOTE_ADDR} ^10\.32\.64\.250
  RewriteRule ^ - [NC,F,L]
</IfModule>

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteCond %{HTTP_REFERER} ^http(?:s)?://(?:.*)?\.(?:cc|eu|ru)(?:/.*)?$ [NC,OR]
  RewriteCond %{HTTP_REFERER} ^http(?:s)?://(?:.*\.)?(?:example1.com|example2.com)(?:/.*)?$ [NC]
  RewriteRule ^ - [NC,F,L]
</IfModule>

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteCond %{SERVER_PROTOCOL} ^HTTP/1\.0$
  RewriteRule ^ - [NC,F,L]
</IfModule>

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteCond %{HTTP_USER_AGENT} ^(?:\s|-)*$
  RewriteRule ^ - [NC,F,L]
</IfModule>

<FilesMatch "wp-config\.php|xmlrpc\.php|error_log|readme\.html|license\.txt|wp-config-sample\.php">
  Order allow,deny
  Require all denied
</FilesMatch>

<FilesMatch "wp-login\.php">
  Order deny,allow
  deny from all
  allow from 172.16.0.1
  allow from 172.30.254.1
</FilesMatch>

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteCond %{HTTP_REFERER} !^$
  RewriteCond %{HTTP_REFERER} !^http(?:s)?://(?:.*\.)?dalesandro\.net(?:/.*)?$ [NC]
  RewriteRule \.(?:jpe?g|gif|png|svg|webp|zip|rar|pdf)$ - [NC,F,L]
</IfModule>

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /
  RewriteRule ^wp-admin/includes/ - [NC,F,L]
  RewriteRule !^wp-includes/ - [S=3]
  RewriteRule ^wp-includes/[^/]+\.php$ - [NC,F,L]
  RewriteRule ^wp-includes/js/tinymce/langs/.+\.php - [NC,F,L]
  RewriteRule ^wp-includes/theme-compat/ - [NC,F,L]
</IfModule>

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /
  RewriteCond %{QUERY_STRING} (author=\d+) [NC]
  RewriteRule ^ - [NC,F,L]
</IfModule>

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteCond %{REQUEST_URI} ^/post-permalink(?:/.*)?$ [OR,NC]
  RewriteCond %{REQUEST_URI} ^/2021/02/28/another-post-permalink(?:/.*)?$ [OR,NC]
  RewriteCond %{REQUEST_URI} ^/yet-another-post-permalink(?:/.*)?$ [NC]
  RewriteRule ^ - [NC,G,L]
</IfModule>

RedirectMatch 301 ^/post-permalink(?:/.*)?$ https://www.dalesandro.net/updated-post-permalink/
RedirectMatch 301 ^/another-post-permalink(?:/.*)?$ https://www.dalesandro.net/replacement-permalink/

RedirectMatch 301 ^/([0-9]{4})/([0-9]{2})/([0-9]{2})/(?:.*)$ https://www.dalesandro.net/$4

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteCond %{REQUEST_METHOD} POST [NC]
  RewriteCond %{REQUEST_URI} wp-comments-post\.php [NC]
  RewriteCond %{HTTP_REFERER} !^http(s)?://(.*\.)?dalesandro\.net(/.*)?$ [OR,NC]
  RewriteCond %{HTTP_USER_AGENT} ^(?:\s|-)*$
  RewriteRule ^ - [NC,F,L]
</IfModule>

<IfModule mod_headers.c>
  Header set X-XSS-Protection "1; mode=block"
  Header set X-Frame-Options "DENY"
  Header set X-Content-Type-Options nosniff
  Header set Referrer-Policy "same-origin"
</IfModule>

<IfModule mod_deflate.c>
  SetOutputFilter DEFLATE
  <IfModule mod_setenvif.c>
    SetEnvIfNoCase Request_URI \.(?:jpe?g|gif|png|svg|webp|zip|rar|pdf)$ no-gzip dont-vary
  </IfModule>
</IfModule>

<IfModule mod_expires.c>
  ExpiresActive On
  ExpiresDefault "access plus 1 month"
  ExpiresByType text/cache-manifest "access plus 0 seconds"
  ExpiresByType text/html "access plus 0 seconds"
  ExpiresByType text/xml "access plus 0 seconds"
  ExpiresByType application/xml "access plus 0 seconds"
  ExpiresByType application/json "access plus 0 seconds"
  ExpiresByType application/rss+xml "access plus 1 hour"
  ExpiresByType application/atom+xml "access plus 1 hour"
  ExpiresByType image/x-icon "access plus 1 year"
  ExpiresByType image/gif "access plus 1 year"
  ExpiresByType image/png "access plus 1 year"
  ExpiresByType image/jpg "access plus 1 year"
  ExpiresByType image/jpeg "access plus 1 year"
  ExpiresByType image/webp "access plus 1 year"
  ExpiresByType image/svg+xml "access plus 1 year"
  ExpiresByType image/vnd.microsoft.icon "access plus 1 year"
  ExpiresByType video/ogg "access plus 1 year"
  ExpiresByType audio/ogg "access plus 1 year"
  ExpiresByType video/mp4 "access plus 1 year"
  ExpiresByType video/webm "access plus 1 year"
  ExpiresByType video/mpeg "access plus 1 year"
  ExpiresByType text/x-component "access plus 1 year"
  ExpiresByType font/ttf "access plus 1 year"
  ExpiresByType font/otf "access plus 1 year"
  ExpiresByType font/woff "access plus 1 year"
  ExpiresByType font/woff2 "access plus 1 year"
  ExpiresByType application/font-woff "access plus 1 year"
  ExpiresByType application/x-font-ttf "access plus 1 year"
  ExpiresByType font/opentype "access plus 1 year"
  ExpiresByType application/vnd.ms-fontobject "access plus 1 year"
  ExpiresByType text/css "access plus 1 month"
  ExpiresByType text/javascript "access plus 1 month"
  ExpiresByType text/x-javascript "access plus 1 month"
  ExpiresByType application/javascript "access plus 1 month"
  ExpiresByType application/x-javascript "access plus 1 month"
  ExpiresByType application/pdf "access plus 1 month"
  ExpiresByType application/zip "access plus 1 month"
  <IfModule mod_headers.c>
    Header append Cache-Control "public"
  </IfModule>
</IfModule>

# BEGIN WordPress
# The directives (lines) between "BEGIN WordPress" and "END WordPress" are
# dynamically generated, and should only be modified via WordPress filters.
# Any changes to the directives between these markers will be overwritten.
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>

# END WordPress