6 ways to a faster and safer WordPress

Having a WordPress website is often great, but it also makes you vulnerable for all kinds of attacks and mischief – Or maybe not vulnerable, but it invites many wrong doers to try and attack you. You’re an easy target. Let’s make it a bit less easy for them without using plugins!

Recently I’ve been plagued on another website by slowness, the occasional downtime and other annoying stuff. Paying more attention to usage stats and the error_log it turns out there was a bunch of stuff going on. A few of IP Addresses constantly tried something with wp-login.php and some other pages and files being loaded over and over again for no apparent reason.

Another issue was the RSS feed WordPress generates. Sure, it works fine. But if you get almost 10000+ requests on it per hour, that’ll slow things down, too. Sometimes.

So I did some research and have come up with a few things to try and prevent this kind of behavior. Of-course it’s no use blocking IP addresses but you can prevent access to things or if they access those things lessen the load on your server a great deal.

Note: In this article we’ll edit a few files essential to your WordPress site. Namely the .htaccess file and wp-config.php. Doing this wrong will cause your site to malfunction in one way or another. So be sure you make a backup of these files before doing anything.

If you’re not comfortable editing such files or you’re not sure how any of this works ask someone to help you!

.htaccess file

The .htaccess file is not a WordPress file, but an important file none-the-less. It allows you to manipulate the server configuration without actually changing the server. This file allows for advanced control over who access what and in which way. This file is therefor very important to your website and server.

wp-config.php file

This is the main configuration file for WordPress. Without it, WordPress doesn’t know what WordPress is. Your login details to the database are in here, as well as security salts and you can manipulate advanced settings and variables here.

Here are 6 ways to keep your WordPress website safer and more faster without using plugins. Because plugins slow your site down as well.

1. Protect your dashboard

Because WordPress is so visible there is a high chance you experience this too. People trying to log in to your site. The occasional dumbass trying a common password on the Admin account is no big deal, but bots and botnets trying this all day long, hundreds of times per hour. That IS a problem.

So to counter this I have found and optimised these access rules to not only hide the real login url, but also require a semi secret hash to get in.
I’ve placed these in my .htaccess file, right above the ones from WordPress

RewriteEngine On
RewriteBase /
# Bruteforce Protection
RewriteRule ^signin wp-login.php?stealth_in=12345&redirect_to=http://www.YOURDOMAIN.com/wp-admin/ [R,L]
RewriteRule ^signout wp-login.php?action=logout&_wpnonce=a3d57642ab&stealth_out=67890 [L]
RewriteRule ^admin wp-admin/?stealth_admin=45678 [R,L]
RewriteCond %{HTTP_REFERER} !^http://www.YOURDOMAIN.com/signin 
RewriteCond %{HTTP_REFERER} !^http://www.YOURDOMAIN.com/wp-login\.php 
RewriteCond %{HTTP_REFERER} !^http://www.YOURDOMAIN.com/admin 
RewriteCond %{HTTP_REFERER} !^http://www.YOURDOMAIN.com/wp-admin 
RewriteCond %{QUERY_STRING} !^stealth_in=12345 
RewriteCond %{QUERY_STRING} !^stealth_out=67890 
RewriteCond %{QUERY_STRING} !^stealth_admin=45678
RewriteRule ^wp-login\.php - [R=403,NC,L]
RewriteCond %{QUERY_STRING} ^loggedout=true 
RewriteRule ^wp-login\.php http://www.YOURDOMAIN.com [L] 
RewriteCond %{QUERY_STRING} !^stealth_cron=87654 
RewriteRule ^wp-cron\.php - [R=403,NC,L]

Basically what this does is redirect anything (including you) away from the login page giving them a 403 Permission Denied error page. Unless you use the new url.

To log in: http://www.YOURDOMAIN.com/signin
To log out: http://www.YOURDOMAIN.com/signout
Dashboard: http://www.YOURDOMAIN.com/admin

Ofcourse, you should change the simple numeric codes (12345, 67890, etc.) to something more unique and advanced, too. Anything alphanumeric works, as long as it’s hard to guess, similar to passwords.

On top of this I have blocked ordinary wp-cron.php access. So nobody can flood your site with requests to that, which may overload your database. This may break actual wp-cron from working. But we’ll fix that in Item 6 on this page.

To use cron: http://www.YOURDOMAIN.com/wp-cron.php?stealth_cron=87654

Without the stealth_cron parameter, it just won’t work anymore.

Why is this useful? Because, even if nobody can log in or use cron – WordPress is still loading, using up a ton of resources. Now you block access BEFORE WordPress gets loaded, thus less resources are used.

2. Redirect RSS feeds

Having a lot of readers is great, having a lot of requests to your RSS feeds is not so great. Especially in WordPress, which doesn’t generate a feed file but generates a dynamic thing every time you load the url. While not super bad – It means that instead of just loading a file, WordPress has to process the request and compile the same thing over and over again from the database. A lot of resources are wasted that way – Resources better used to serve content to actual visitors.

My plugins all ping my RSS feeds from the dashboard. This gives me great exposure for new posts. But it also causes a lot of strain on my sites resources with tens of thousands of plugin users this amounts to /feed/ being one of the busiest things on my website.

I’ve redirected the bulk of that to Google Feedburner. But what about people who don’t know about feedburner and just enter www.YOURDOMAIN.com/feed/? Simple, redirect them too.

I’ve entered this right below the Bruteforce rules from Item 1, but still above the WordPress bit in my .htaccess file.

# RSS feeds
RewriteCond %{HTTP_USER_AGENT} !FeedBurner [NC]
RewriteCond %{HTTP_USER_AGENT} !FeedValidator [NC]
RewriteRule ^feed/?([_0-9a-z-]+)?/?$ http://feeds.feedburner.com/YOURFEED [R=302,NC,L]

This redirects anything but Feedburner to Feedburner. So Feedburner can grab the new content. But actual subscribers and readers are now forced to use Feedburner.

Simple and effective.

3. Use Browser caching

When you visit a site, part of it is downloaded and stored locally on your computer. To be deleted after you leave the site. From your server you can ask the browser to keep certain files for a longer period of time. For example common scripts or images, because they don’t change anyway. This saves bandwidth and requests on your server. Making your site load faster, but also reducing server load to some extent.

I’ve added these rules to my .htaccess file, above all the Rewrite rules stuff from Item 1 and 2. So right at the top of the file.

# Use UTF-8 encoding for anything served text/plain or text/html
AddDefaultCharset UTF-8
AddCharset UTF-8 .atom .css .js .json .rss .vtt .xml
# FileETag None is not enough for every server.
Header unset ETag
FileETag None
# Send CORS headers if browsers request them; enabled by default for images.
SetEnvIf Origin ":" IS_CORS
Header set Access-Control-Allow-Origin "*" env=IS_CORS
# Allow access to web fonts from all domains.
Header set Access-Control-Allow-Origin "*"
Header unset Pragma
Header append Cache-Control "public"
Header unset Last-Modified
# Cache Control
ExpiresActive on
ExpiresDefault                          "access plus 1 month"
# cache.appcache needs re-requests in FF 3.6
ExpiresByType text/cache-manifest       "access plus 0 seconds"
# Your document html
ExpiresByType text/html                 "access plus 0 seconds"
# Data
ExpiresByType text/xml                  "access plus 0 seconds"
ExpiresByType application/xml           "access plus 0 seconds"
ExpiresByType application/json          "access plus 0 seconds"
# Feed
ExpiresByType application/rss+xml       "access plus 1 hour"
ExpiresByType application/atom+xml      "access plus 1 hour"
# Favicon (cannot be renamed)
ExpiresByType image/x-icon              "access plus 1 week"
# Media: images, video, audio
ExpiresByType image/gif                 "access plus 1 month"
ExpiresByType image/png                 "access plus 1 month"
ExpiresByType image/jpg 		"access 1 year"
ExpiresByType image/jpeg 		"access 1 year"
ExpiresByType video/ogg                 "access plus 1 month"
ExpiresByType audio/ogg                 "access plus 1 month"
ExpiresByType video/mp4                 "access plus 1 month"
ExpiresByType video/webm                "access plus 1 month"
# HTC files (css3pie)
ExpiresByType text/x-component          "access plus 1 month"
# Webfonts
ExpiresByType application/x-font-ttf    "access plus 1 month"
ExpiresByType font/opentype             "access plus 1 month"
ExpiresByType application/x-font-woff   "access plus 1 month"
ExpiresByType application/x-font-woff2  "access plus 1 month"
ExpiresByType image/svg+xml             "access plus 1 month"
ExpiresByType application/vnd.ms-fontobject "access plus 1 month"
# CSS and JavaScript
ExpiresByType text/css                  "access plus 1 year"
ExpiresByType application/javascript    "access plus 1 year"
# Add correct content-type for fonts 
AddType application/vnd.ms-fontobject .eot
AddType font/ttf .ttf
AddType font/otf .otf
AddType font/x-woff .woff
AddType image/svg+xml .svg
# Add a far future Expires header for fonts
ExpiresByType application/vnd.ms-fontobject "access plus 1 year"
ExpiresByType font/ttf "access plus 1 year"
ExpiresByType font/otf "access plus 1 year"
ExpiresByType font/x-woff "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 year"

4. Gzip Compression

Gzip compression compresses the contents of your site when it’s sent to the browser. This reduces bandwidth and makes your site appear faster. Most modern browsers support this these days and most servers do, too.

I’ve added this to the .htaccess file right below the Browser Caching stuff of the previous item.

# Gzip compression
SetOutputFilter DEFLATE
# Force deflate for mangled headers
SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding
RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding
# Don’t compress images and other uncompressible content
SetEnvIfNoCase Request_URI \
\.(?:gif|jpe?g|png|rar|zip|exe|flv|mov|wma|mp3|avi|swf|mp?g|mp4|webm|webp)$ no-gzip dont-vary
# Compress all output labeled with one of the following MIME-types
AddOutputFilterByType DEFLATE application/atom+xml application/javascript application/json application/rss+xml application/vnd.ms-fontobject application/x-font-ttf application/xhtml+xml application/xml font/opentype image/svg+xml image/x-icon text/css text/html text/plain text/x-component text/xml
Header append Vary: Accept-Encoding
# Compress compressible fonts
AddOutputFilterByType DEFLATE font/ttf font/otf image/svg+xml

5. Increase the memory limit

Giving WordPress a little more wiggle room can be a good thing. Many hosting providers limit websites to 64MB ram or something similar. Sometimes you can configure this in your hosting dashboard (cPanel, Plesk or whatever). Often times you can not. That doesn’t mean you can’t change the memory limit.

In your wp-config.php, around line 52 (below the salts) I’ve added this line:

define('WP_MEMORY_LIMIT', '192M');

This ups the memory to 192MB. Similar to PC memory, this lets PHP do more stuff at the same time and work a little faster. This is more useful for larger sites, such as this one, or for sites with many plugins or if you use large plugins such as WooCommerce. For a basic, low traffic blog you probably won’t notice a difference.

Note: Increasing memory is fine, but only if you need it, upping the limit because your site is a unoptimized pile of junk code is not a valid reason of-course.

6. Disable WP-Cron

WP-Cron is a system where WordPress deals with background tasks. Plugins and themes can use this for all kinds of stuff. My AdRotate plugin for example cleans up stats with it for example. And my Analytics Spam Blocker plugin downloads new blocklists using wp-cron.

That’s all fine and useful, but does it need to run on every page load? Probably not. However, that’s how wp-cron works. Every time someone accesses your website, wp-cron is triggered and checks if there’s something to do in the background.

In wp-config.php right below the salts, where we added the memory limit from the Item 5 I’ve added this:

define('DISABLE_WP_CRON', 1);

This disables wp-cron. Removing the line, or changing the 1 to 0, enabled it again.

To schedule a real cron job you’ll have to log in to your hosting dashboard and schedule one there. Sometimes this is called simply ‘Scheduled tasks’, sometimes it’s called ‘Tasks’ or just ‘Cron Jobs’.

Schedule the job for whatever interval you want. This can be every minute to every hour, or even daily or weekly. It’s up to you really. I have mine set to every 15 minutes.

And for the command, you simply call: http://www.YOURDOMAIN.com/wp-cron.php .
If you use the Brute force protection from Item 1 you should add the secret key like so: http://www.YOURDOMAIN.com/wp-cron.php?stealth_cron=87654 .
(Of-course with the right secret key)

If you find this article useful, I’ve written an updated version in 2023: Protect and Speed up your WordPress website.