Last Revised: October 2, 2021
If you use Apache HTTPD or LiteSpeed or any system based on these web servers you may be interested in configuring the .htaccess file of the root of your WordPress configuration in a complex way (more than the one that comes by default).
- Detailed configuration by blocks
- Default files
- Canonical domain
- Blocking “rare” requests
- Blocking access to files with data
- Important WordPress files
- Blocking user listing
- Blocking folder listings
- Blocking “insecure” files
- Other blockages
- Mitigation CVE-2018-6389
- Blocking XML-RPC
- Blocking PHP execution in uploads
- Controlling wp-includes
- Simple control of XSS attacks
- Default WordPress Code
- Improve insecure requests
- Static cache
- Content compression
- Full content of .htaccess
- Seguir con Seguridad para WordPress
- About this document
Detailed configuration by blocks
HTTPS via Proxy
It is possible that there is a proxy in front of your website, so if so, we must inform the system to tell it that it is already activated and not to run certain elements.
SetEnvIf X-Forwarded-Proto https HTTPS=on
PageSpeed Module
Although it can be a good solution in certain cases, by default we will configure the PageSpeed Module inactively by default since there will be several configurations included that this module makes in our configuration.
<IfModule pagespeed_module>
ModPagespeed off
</IfModule>
Locking server files
By default we will block external access to the configuration files, and this one.
<FilesMatch "^.ht">
Deny from all
</FilesMatch>
<FilesMatch "^.ftp">
Deny from all
</FilesMatch>
<FilesMatch "^php.ini$">
Deny from all
</FilesMatch>
<FilesMatch "^.well-known">
Allow from all
</FilesMatch>
Enable CORS for some types
In some cases font files need to be called from different
<FilesMatch ".(?:ttf|eot|woff|otf)$">
Header set Access-Control-Allow-Origin "*"
</FilesMatch>
Disable directory listing
Some default systems allow that when accessing a folder that does not have a default execution file it shows all the contents. If this is the case, we can disable this option by default.
<IfModule mod_autoindex.c>
Options -Indexes
</IfModule>
Default files
By default we will configure that the web server makes calls to the index.php, and ultimately, to call the main WordPress file. In addition to telling you to follow and use the SymLinks.
DirectoryIndex index.php index.html /index.php
Options None
Options FollowSymLinks
ServerSignature Off
Canonical domain
Although WordPress manages redirects and main domains well, in cases where we only use one domain, it is best to do that management controlled from the .htaccess. In this case we configure three elements.
We configure the main domain and perform/force its use. Later we check whether or not HTTPS is activated and, if the request is not being made, it is forced.
RewriteCond %{HTTP_HOST} !www.example.com
RewriteRule (.*) https://www.example.com%{REQUEST_URI} [L,R=301]
RewriteCond %{HTTPS} !on
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
Blocking “rare” requests
As a general rule, GET and POST requests are usually made, including HEAD. Other types of requests are usually strange and in very few cases will be necessary for the use of WordPress.
RewriteCond %{REQUEST_METHOD} ^(TRACE|TRACK)
RewriteRule .* - [F]
Blocking access to files with data
Some common files in WordPress include specific information about versions and some data. Thanks to this code we block access to reading or license files.
RewriteRule readme.(html|txt) - [L,R=404]
RewriteRule (licencia|license|LICENSE|olvasdel|lisenssi|liesmich).(html|txt) - [L,R=404]
Important WordPress files
There are some files that due to the information they contain or their lack of use may be interesting to block. The main one is the wp-config which includes a lot of information. Others are the wp-cron (which is recommended to run through a task manager, the WordPress installation files (once the installation is done these are not necessary to run) and a historical link management file (which most installations do not use).
RewriteRule ^wp-config - [L,R=404]
RewriteRule ^wp-cron.php - [L,R=404]
RewriteRule ^wp-admin/(install|setup-config|upgrade).php - [L,R=404]
RewriteRule ^wp-admin/maint/repair.php - [L,R=404]
RewriteRule ^wp-links-opml.php$ - [L,R=404]
Blocking user listing
Although it is not a serious item that you know the list of users on a site, in some cases you may not want to access the tabs or pages in the list of users. To avoid this we can block the usual accesses that includes the system of permanent links itself.
RewriteCond %{QUERY_STRING} ^author= [NC]
RewriteRule .* - [F,L]
RewriteRule ^author/ - [F,L]
Blocking folder listings
One of the ways of detecting the existence of elements (plugins, themes…) is the detection of the existence of folders. In order for pentesting tools to have more problems, it is better to apply a 404 code.
RewriteRule ^wp-content/mu-plugins/$ - [L,R=404]
RewriteRule ^wp-content/(plugins|themes)/(.+)/$ - [L,R=404]
Blocking “insecure” files
In some folders there are a number of elements that, as a general rule, do not have to be or executed.
RewriteRule ^wp-content/(?:uploads|files)/.+.(html|js|php|shtml|swf)$ - [L,R=403]
RewriteRule ^wp-content/plugins/.+.(aac|avi|bz2|cur|docx?|eot|exe|flv|gz|heic|htc|m4a|midi?|mov|mp3|mp4|mpe?g|ogg|ogv|otf|pdf|pptx?|rar|rtf|tar|tgz|tiff?|ttc|wav|wmv|xlsx?|zip) - [L,R=404]
Other blockages
Some general configuration files, logs and the like that as a general rule do not have to be accessible from outside.
RewriteRule ^sftp-config.json - [L,R=404]
RewriteRule (access|error)_log - [L,R=404]
RewriteRule installer-log.txt - [L,R=404]
RewriteRule wp-content/debug.log - [L,R=404]
RewriteRule (^#.*#|.(bak|config|dist|fla|inc|ini|log|psd|sh|sql|sw[op])|~)$ - [L,R=404]
Mitigation CVE-2018-6389
As a general rule, it is better to block the possible attack that can be carried out easily with the loading of all the scripts. To avoid this it is better to block its charge.
<FilesMatch "load-(scripts|styles).php">
Deny from all
</FilesMatch>
Blocking XML-RPC
If you do not use this technology it is better to block it and prevent attacks or inconvenient access. It is usually used with the WordPress App or to perform Pingbacks.
<FilesMatch "xmlrpc.php">
Deny from all
</FilesMatch>
Blocking PHP execution in uploads
There should be no PHP files in the file upload folder, but it is a place where many attacks occur. In this case we will block its execution.
<FilesMatch "wp-content/uploads/(.+).php">
Deny from all
</FilesMatch>
Controlling wp-includes
In the folder of the includes there are usually elements that do not usually run externally, and therefore, in general, it is better to block their execution.
RewriteRule ^wp-admin/includes/ - [F,L]
RewriteRule !^wp-includes/ - [S=3]
RewriteRule ^wp-includes/[^/]+.php$ - [F,L]
RewriteRule ^wp-includes/js/tinymce/langs/.+.php - [F,L]
RewriteRule ^wp-includes/theme-compat/ - [F,L]
Simple control of XSS attacks
Avoid very simple cross-scripting attacks using the URL.
RewriteCond %{QUERY_STRING} (<|%3C).*script.*(>|%3E) [NC,OR]
RewriteCond %{QUERY_STRING} GLOBALS(=|[|%[0-9A-Z]{0,2}) [OR]
RewriteCond %{QUERY_STRING} _REQUEST(=|[|%[0-9A-Z]{0,2})
RewriteRule ^(.*)$ index.php [F,L]
An important detail is that if within the panel you manage advertising codes or other types of scripts, this system can be very aggressive according to the second line (the one that includes the scripts), so it could be eliminated.
Default WordPress Code
We integrate the code that recommends us and usually generates the WordPress itself.
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteRule ^index.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
Improve insecure requests
With this, all internal requests made to the site from HTTP will automatically pass to HTTPS. In addition, other systems are added to force navigation to be done exclusively by HTTPS (HSTS).
<IfModule mod_headers.c>
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
Header always set Content-Security-Policy: upgrade-insecure-requests;
Header always set X-XSS-Protection "1; mode=block"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "DENY"
Header unset Pragma
Header always unset WP-Super-Cache
Header always unset X-Pingback
</IfModule>
Static cache
We can activate a cache similar to that performed by CDNs in users’ browsers.
<IfModule mod_expires.c>
ExpiresActive on
#Varios
ExpiresByType application/pdf A2592000
ExpiresByType image/x-icon A2592000
ExpiresByType image/vnd.microsoft.icon A2592000
ExpiresByType image/svg+xml A2592000
#Imagenes
ExpiresByType image/jpg A2592000
ExpiresByType image/jpeg A2592000
ExpiresByType image/png A2592000
ExpiresByType image/gif A2592000
ExpiresByType image/webp A2592000
#Media
ExpiresByType video/ogg A2592000
ExpiresByType audio/ogg A2592000
ExpiresByType video/mp4 A2592000
ExpiresByType video/webm A2592000
#CSS/JS
ExpiresByType text/css A2592000
ExpiresByType text/javascript A2592000
ExpiresByType application/javascript A2592000
ExpiresByType application/x-javascript A2592000
#Fuentes
ExpiresByType application/x-font-ttf A2592000
ExpiresByType application/x-font-woff A2592000
ExpiresByType application/font-woff A2592000
ExpiresByType application/font-woff2 A2592000
ExpiresByType application/vnd.ms-fontobject A2592000
ExpiresByType font/ttf A2592000
ExpiresByType font/woff A2592000
ExpiresByType font/woff2 A2592000
</IfModule>
Content compression
We can reduce the size of the files that can be compressed (mainly texts).
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
AddOutputFilterByType DEFLATE application/json
AddOutputFilterByType DEFLATE application/ld+json
AddOutputFilterByType DEFLATE application/manifest+json
AddOutputFilterByType DEFLATE application/rdf+xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/schema+json
AddOutputFilterByType DEFLATE application/vnd.geo+json
AddOutputFilterByType DEFLATE application/x-web-app-manifest+json
AddOutputFilterByType DEFLATE application/vnd.ms-fontobject
AddOutputFilterByType DEFLATE application/x-font-ttf
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE font/opentype
AddOutputFilterByType DEFLATE font/eot
AddOutputFilterByType DEFLATE image/bmp
AddOutputFilterByType DEFLATE image/svg+xml
AddOutputFilterByType DEFLATE image/x-icon
AddOutputFilterByType DEFLATE image/vnd.microsoft.icon
AddOutputFilterByType DEFLATE text/javascript
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/x-component
AddOutputFilterByType DEFLATE text/xml
</IfModule>
Full content of .htaccess
The complete file would be as follows:
# BEGIN HTTPS via Proxy
SetEnvIf X-Forwarded-Proto https HTTPS=on
# END HTTPS via Proxy
# BEGIN ModPagespeed
<IfModule pagespeed_module>
ModPagespeed off
</IfModule>
# END ModPagespeed
# START - [Seguridad] Bloqueado el acceso a ficheros ".ht"
<FilesMatch "^.ht">
Deny from all
</FilesMatch>
<FilesMatch "^.ftp">
Deny from all
</FilesMatch>
<FilesMatch "^php.ini$">
Deny from all
</FilesMatch>
<FilesMatch "^.well-known">
Allow from all
</FilesMatch>
# END
# BEGIN cors
<FilesMatch ".(?:ttf|eot|woff|otf)$">
Header set Access-Control-Allow-Origin "*"
</FilesMatch>
#END
# BEGIN Directory browsing
<IfModule mod_autoindex.c>
Options -Indexes
</IfModule>
# END Directory browsing
DirectoryIndex index.php index.html /index.php
Options None
Options FollowSymLinks
ServerSignature Off
# START Expresión regular de WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
# [Seguridad] Configuracion del dominio canonico
RewriteCond %{HTTP_HOST} !www.example.com
RewriteRule (.*) https://www.example.com%{REQUEST_URI} [L,R=301]
RewriteCond %{HTTPS} !on
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# [Seguridad] Bloqueamos la peticiones TRACE y TRACK
RewriteCond %{REQUEST_METHOD} ^(TRACE|TRACK)
RewriteRule .* - [F]
# [Seguridad] Ficheros estáticos generales
RewriteRule readme.(html|txt) - [L,R=404]
RewriteRule (licencia|license|LICENSE|olvasdel|lisenssi|liesmich).(html|txt) - [L,R=404]
# [Seguridad] Ficheros propios de WordPress
RewriteRule ^wp-config - [L,R=404]
RewriteRule ^wp-cron.php - [L,R=404]
RewriteRule ^wp-admin/(install|setup-config|upgrade).php - [L,R=404]
RewriteRule ^wp-admin/maint/repair.php - [L,R=404]
RewriteRule ^wp-links-opml.php$ - [L,R=404]
# [Seguridad] Bloqueo del listing de usuarios
RewriteCond %{QUERY_STRING} ^author= [NC]
RewriteRule .* - [F,L]
RewriteRule ^author/ - [F,L]
# [Seguridad] Bloqueo de listados de carpetas
RewriteRule ^wp-content/mu-plugins/$ - [L,R=404]
RewriteRule ^wp-content/(plugins|themes)/(.+)/$ - [L,R=404]
# [Seguridad] Bloqueo de ficheros inseguros
RewriteRule ^wp-content/(?:uploads|files)/.+.(html|js|php|shtml|swf)$ - [L,R=403]
RewriteRule ^wp-content/plugins/.+.(aac|avi|bz2|cur|docx?|eot|exe|flv|gz|heic|htc|m4a|midi?|mov|mp3|mp4|mpe?g|ogg|ogv|otf|pdf|pptx?|rar|rtf|tar|tgz|tiff?|ttc|wav|wmv|xlsx?|zip) - [L,R=404]
# [Seguridad] Otros bloqueos
RewriteRule ^sftp-config.json - [L,R=404]
RewriteRule (access|error)_log - [L,R=404]
RewriteRule installer-log.txt - [L,R=404]
RewriteRule wp-content/debug.log - [L,R=404]
RewriteRule (^#.*#|.(bak|config|dist|fla|inc|ini|log|psd|sh|sql|sw[op])|~)$ - [L,R=404]
</IfModule>
# END
# START - [Seguridad] Mitigation CVE-2018-6389
<FilesMatch "load-(scripts|styles).php">
Deny from all
</FilesMatch>
# END
# START - [Seguridad] XML-RPC
<FilesMatch "xmlrpc.php">
Deny from all
</FilesMatch>
# END
# START - [Seguridad] No ejecutar ficheros en Uploads
<FilesMatch "wp-content/uploads/(.+).php">
Deny from all
</FilesMatch>
# END
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
### START WP includes
RewriteRule ^wp-admin/includes/ - [F,L]
RewriteRule !^wp-includes/ - [S=3]
RewriteRule ^wp-includes/[^/]+.php$ - [F,L]
RewriteRule ^wp-includes/js/tinymce/langs/.+.php - [F,L]
RewriteRule ^wp-includes/theme-compat/ - [F,L]
### END WP includes
### START SQL Injection
RewriteCond %{QUERY_STRING} (<|%3C).*script.*(>|%3E) [NC,OR]
RewriteCond %{QUERY_STRING} GLOBALS(=|[|%[0-9A-Z]{0,2}) [OR]
RewriteCond %{QUERY_STRING} _REQUEST(=|[|%[0-9A-Z]{0,2})
RewriteRule ^(.*)$ index.php [F,L]
### END SQL Injection
RewriteRule ^index.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
# BEGIN Security Headers
<IfModule mod_headers.c>
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
Header always set Content-Security-Policy: upgrade-insecure-requests;
Header always set X-XSS-Protection "1; mode=block"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "DENY"
Header unset Pragma
Header always unset WP-Super-Cache
Header always unset X-Pingback
</IfModule>
# END Strict-Transport-Security
# BEGIN EXPIRES
<IfModule mod_expires.c>
ExpiresActive on
#Varios
ExpiresByType application/pdf A2592000
ExpiresByType image/x-icon A2592000
ExpiresByType image/vnd.microsoft.icon A2592000
ExpiresByType image/svg+xml A2592000
#Imagenes
ExpiresByType image/jpg A2592000
ExpiresByType image/jpeg A2592000
ExpiresByType image/png A2592000
ExpiresByType image/gif A2592000
ExpiresByType image/webp A2592000
#Media
ExpiresByType video/ogg A2592000
ExpiresByType audio/ogg A2592000
ExpiresByType video/mp4 A2592000
ExpiresByType video/webm A2592000
#CSS/JS
ExpiresByType text/css A2592000
ExpiresByType text/javascript A2592000
ExpiresByType application/javascript A2592000
ExpiresByType application/x-javascript A2592000
#Fuentes
ExpiresByType application/x-font-ttf A2592000
ExpiresByType application/x-font-woff A2592000
ExpiresByType application/font-woff A2592000
ExpiresByType application/font-woff2 A2592000
ExpiresByType application/vnd.ms-fontobject A2592000
ExpiresByType font/ttf A2592000
ExpiresByType font/woff A2592000
ExpiresByType font/woff2 A2592000
</IfModule>
# END EXPIRES
# BEGIN HttpHeadersCompression
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
AddOutputFilterByType DEFLATE application/json
AddOutputFilterByType DEFLATE application/ld+json
AddOutputFilterByType DEFLATE application/manifest+json
AddOutputFilterByType DEFLATE application/rdf+xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/schema+json
AddOutputFilterByType DEFLATE application/vnd.geo+json
AddOutputFilterByType DEFLATE application/x-web-app-manifest+json
AddOutputFilterByType DEFLATE application/vnd.ms-fontobject
AddOutputFilterByType DEFLATE application/x-font-ttf
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE font/opentype
AddOutputFilterByType DEFLATE font/eot
AddOutputFilterByType DEFLATE image/bmp
AddOutputFilterByType DEFLATE image/svg+xml
AddOutputFilterByType DEFLATE image/x-icon
AddOutputFilterByType DEFLATE image/vnd.microsoft.icon
AddOutputFilterByType DEFLATE text/javascript
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/x-component
AddOutputFilterByType DEFLATE text/xml
</IfModule>
# END HttpHeadersCompression
Seguir con Seguridad para WordPress
Actual
About this document
This document is regulated by the EUPL v1.2 license, published in WP SysAdmin and created by Javier Casares. Please, if you use this content in your website, your presentation or any material you distribute, remember to mention this site or its author, and having to put the material you create under EUPL license.