Basics about .htaccess in WordPress

The .htaccess file (hypertext access) lets you manage the configuration of your web server. Even a small change in this file can break and take down an entire website. Therefore it is always recommended to take a backup before touching the .htaccess file. The best way is to just use an FTP client like Filezilla and download the file to your local computer before even opening it.

We can use the .htaccess file as follows…

  • Redirect rules
  • Blocking and allowing access to the site from defined IP addresses
  • Password protect individual folders on your web server
  • Performance improvement by enabling compression and caching
  • Overwriting PHP.ini settings (e.g. maximum upload size)
  • … many other

Default .htaccess file

Every WordPress instance comes with this default .htaccess file located on your root directory (e.g. /home/markus/public_html) on your webserver.

# 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
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress

Force HTTPs via .htaccess

It needs more than just a rule in your .htaccess file to make your site secure. First, you need an SSL certificate, you need the rewrite rule in .htaccess, you need to change the WordPress Address (URL) and Site Address (URL) in your WordPress Backend setting (or wp-config.php, or functions.php, or database), and you need to guarantee that your site does not contain any HTTP links, e.g. to images or other sources you use.

There is a lot to take care of when you want to get your site secured and unfortunately it is absolutely necessary. What’s confusing for a beginner (and even advanced) WordPress user is the possible combination of applying those things. For instance is it possible to force a redirect from HTTP to HTTPs via .htaccess, via hosting provider, and via a CDN like Cloudflare.

WordPress Address (URL)
There are several ways to change your WordPress Address URL
Site Address (URL)
There are several ways to change your Site Address URL

For now, we only have a look at the rules that must be added to the .htaccess file.

Add before # BEGIN WORDPRESS section

RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301,NE]

Full .htaccess file including force HTTPs and default entries

RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301,NE] 

# 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
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress

Enable Deflate Compression (mod_deflate)

! Unclear at the current moment how such settings in .htaccess interfere with WordPress plugins or CDN promising the same results

# BEGIN DEFLATE COMPRESSION
<IfModule mod_deflate.c>
# Compress HTML, CSS, JavaScript, Text, XML and fonts
 AddOutputFilterByType DEFLATE application/javascript
 AddOutputFilterByType DEFLATE application/rss+xml
 AddOutputFilterByType DEFLATE application/vnd.ms-fontobject
 AddOutputFilterByType DEFLATE application/x-font
 AddOutputFilterByType DEFLATE application/x-font-opentype
 AddOutputFilterByType DEFLATE application/x-font-otf
 AddOutputFilterByType DEFLATE application/x-font-truetype
 AddOutputFilterByType DEFLATE application/x-font-ttf
 AddOutputFilterByType DEFLATE application/x-javascript
 AddOutputFilterByType DEFLATE application/xhtml+xml
 AddOutputFilterByType DEFLATE application/xml
 AddOutputFilterByType DEFLATE font/opentype
 AddOutputFilterByType DEFLATE font/otf
 AddOutputFilterByType DEFLATE font/ttf
 AddOutputFilterByType DEFLATE image/svg+xml
 AddOutputFilterByType DEFLATE image/x-icon
 AddOutputFilterByType DEFLATE text/css
 AddOutputFilterByType DEFLATE text/html
 AddOutputFilterByType DEFLATE text/javascript
 AddOutputFilterByType DEFLATE text/plain
 AddOutputFilterByType DEFLATE text/xml
</IfModule>
# END DEFLATE COMPRESSION

Enable GZIP Compression (mod_gzip)

! Unclear at the current moment how such settings in .htaccess interfere with WordPress plugins or CDN promising the same results

# BEGIN GZIP COMPRESSION
<IfModule mod_gzip.c>
mod_gzip_on Yes
mod_gzip_dechunk Yes
mod_gzip_item_include file \.(html?|txt|css|js|php|pl)$
mod_gzip_item_include handler ^cgi-script$
mod_gzip_item_include mime ^text/.*
mod_gzip_item_include mime ^application/x-javascript.*
mod_gzip_item_exclude mime ^image/.*
mod_gzip_item_exclude rspheader ^Content-Encoding:.*gzip.*
</IfModule>
# END GZIP COMPRESSION

Enable expired headers (mod_expires)

Expires headers tell browsers files won’t change for a certain time, meaning those browsers don’t have to reload them each time.

! Unclear at the current moment how such settings in .htaccess interfere with WordPress plugins or CDN promising the same results

#BEGIN EXPIRES HEADERS
<IfModule mod_expires.c>
# Enable expirations
ExpiresActive On
# Default expiration: 1 hour after request
ExpiresDefault "now plus 1 hour"
# CSS and JS expiration: 1 week after request
ExpiresByType text/css "now plus 1 week"
ExpiresByType application/javascript "now plus 1 week"
ExpiresByType application/x-javascript "now plus 1 week"
# Image files expiration: 1 month after request
ExpiresByType image/bmp "now plus 1 month"
ExpiresByType image/gif "now plus 1 month"
ExpiresByType image/jpeg "now plus 1 month"
ExpiresByType image/jp2 "now plus 1 month"
ExpiresByType image/pipeg "now plus 1 month"
ExpiresByType image/png "now plus 1 month"
ExpiresByType image/svg+xml "now plus 1 month"
ExpiresByType image/tiff "now plus 1 month"
ExpiresByType image/vnd.microsoft.icon "now plus 1 month"
ExpiresByType image/x-icon "now plus 1 month"
ExpiresByType image/ico "now plus 1 month"
ExpiresByType image/icon "now plus 1 month"
ExpiresByType text/ico "now plus 1 month"
ExpiresByType application/ico "now plus 1 month"
# Webfonts
ExpiresByType font/truetype "access plus 1 month"
ExpiresByType font/opentype "access plus 1 month"
ExpiresByType application/x-font-woff "access plus 1 month"
ExpiresByType image/svg+xml "access plus 1 month"
ExpiresByType application/vnd.ms-fontobject "access plus 1 month"
</IfModule>
#END EXPIRES HEADERS