This came up back in April in the comments of WordPress Login Protection With .htaccess, where I remarked my .htaccess was pretty long and weird. It came up again when I was doing a MeetWP presentation about hacked sites and some security.
So what is it? Actually less long and weird these days. I’ve been trimming stuff out. But since people ask, here it is, broken out into ‘chunks.’
Security
Everything in this section is for security purposes. That is, I feel it helps my site be safer.
# Tinfoil Hat Stuff
Options +Includes
Options +FollowSymLinks -Indexes
This is basic .htaccess stuff, says to allow includes and symlinks, but stop indexes. This means if you go to halfelf.org/wp-content/uploads/
you don’t see anything, even if I don’t have an index file.
### Blocking Spammers Section ###
<files wp-config.php>
order allow,deny
deny from all
</files>
Now we’re into a little odder bits. This stops anyone from surfing to my wp-config.php file. It shouldn’t matter, PHP won’t let it load the content, but if my PHP is off, it protects me just in case!
# Stop protected folders from being narked. Also helps with spammers
ErrorDocument 401 /401.html
This is because of the next section. It gives a nice error for 401s, which WP normally gets gitty over. And not the fun way.
<IfModule mod_rewrite.c>
# Stop spam attack logins and comments
RewriteEngine On
RewriteCond %{REQUEST_METHOD} POST
RewriteCond %{REQUEST_URI} .(wp-comments-post|wp-login)\.php*
RewriteCond %{HTTP_REFERER} !.*(ipstenu.org|halfelf.org|ipstenu.org|otherplace.net).* [OR]
RewriteCond %{HTTP_USER_AGENT} ^$
RewriteRule (.*) http://%{REMOTE_ADDR}/$ [R=301,L]
# SVN & Git protection
RewriteRule ^(.*/)?(\.svn|\.git)/ - [F,L]
ErrorDocument 403 "Access Forbidden"
</ifModule>
Ahhh, yes. Here I say “If you’re coming to wp-comments-post OR wp-login and you are NOT refereed by one of my domains, sod off.” And then it says “Oh and if you’re looking for .svn or .git files? Go away.” This isn’t perfect, but it works for some of the botnets. The fun part is that the rewrite sends them back to themselves, which should cause annoying things to happen. Don’t want that? Redirect them to fbi.gov. Actually, if some tool had a page “Redirect botnets here…” I would use that, but generally I send them to http://lmgtfy.com/?q=wordpress+botnet
because I’m that sort of kid.
Speed and Bandwidth
Now that I’m safer, lets speed this stuff up!
<IfModule mod_rewrite.c>
RewriteEngine on
# ultimate hotlink protection
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{REQUEST_FILENAME} -f
RewriteCond %{REQUEST_FILENAME} \.(gif|jpe?g?|png)$ [NC]
RewriteCond %{HTTP_REFERER} !^https?://([^.]+\.)?(ipstenu.org|taffys.org|halfelf.org|poohnau.us|ipstenu.org) [NC]
RewriteRule \.(gif|jpe?g?|png)$ - [F,NC,L]
</ifModule>
First up, stop the hotlinks! I got the idea from Perishable Press, and it stops you from embedding my images. This means my site is faster, as you’re not sucking up my bandwidth. I get 5G so it’s not too much of a concern right now, but it’s the principle of the thing. Don’t hotlink images!
### Caching Section ###
# mod_pagespeed
<IfModule pagespeed_module>
ModPagespeed on
ModPagespeedEnableFilters defer_javascript,combine_javascript,move_css_to_head,insert_dns_prefetch,insert_image_dimensions,inline_preview_images,resize_mobile_images
ModPagespeedDisallow */FOLDERNAME/*
ModPagespeedEnableFilters insert_ga
ModPagespeedAnalyticsID UA-MYCODE-4
</IfModule>
I use Pagespeed on my server, so here I’ve added in my extra rules. Not everything is active for all sites. This is my default WP rule-set though, and it works well. I have it skipping a couple non WP folders, who have their own rules inside on their own .htaccess files anyway. If you don’t have pagespeed? Skip this section.
# Expired
<IfModule mod_expires.c>
<Filesmatch "\.(jp?eg|png|gif|ico|woff)$">
ExpiresActive on
ExpiresDefault "access 1 year"
</Filesmatch>
<Filesmatch "\.(css|js|swf|mov|mp3|mpeg|mp4|ogg|ogv|ttf|xml|svg|html)$">
ExpiresActive on
ExpiresDefault "access 1 month"
</Filesmatch>
ExpiresDefault "access 2 days"
</IfModule>
## END EXPIRES ##
Oy. There are a couple ways you can control all these things. One is the way I did (filesmatch) and the other is ExpiresByType image/jpg "access plus 1 year"
. Is one better than they other? I don’t know. Not that I’ve managed to see, but I find the filesmatch to be easier to read and add things too. It’s shorter. Does that make it better? Only in so far as my management goes.
#Gzip
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/text text/html text/plain text/xml text/css application/x-javascript application/javascript text/javascript font/opentype font/truetype font/eot application/x-font-ttf
</IfModule>
#End Gzip
Finally we have gzip, which compresses and makes things smaller and thus faster. Using gzip saves me about 80% in filesize, so it makes things faster to download and, thus, display. If you’re using this in your .htaccess, do not also try to use it in plugins/extensions for other web apps, that way likes double compression and garbage on your pages.
Add support!
This is a really short bit to add in support for filetypes I use that aren’t always standard:
# Add filetypes
AddType application/x-mobipocket-ebook mobi
AddType application/epub+zip epub .epub
AddType video/ogg .ogv
AddType video/mp4 .mp4
AddType video/webm .webm
Rewrites
In general, this is useless to everyone else, save as an example.
### Massive Redirect Section! ###
<IfModule mod_rewrite.c>
RewriteEngine On
# Apple Touch Icons
RewriteRule ^(.*)-precomposed.png /code/images/apple/$1.png [L,R=301]
RewriteRule ^apple-touch-icon(.*) /code/images/apple/apple-touch-icon$1 [L,R=301]
# Ipstenu Moves
RewriteCond %{HTTP_HOST} ^blog\.ipstenu\.org
RewriteRule ^(.*) https://ipstenu.org/$1 [L,R=301]
RewriteCond %{HTTP_HOST} ^ipstenu\.org
RewriteRule ^blog/([0-9]{4})/([0-9]{2})/(.*)$ https://ipstenu.org/$1/$3 [L,R=301]
RewriteCond %{HTTP_HOST} ^ipstenu\.org
RewriteRule ^blog/(.*)$ https://ipstenu.org/$1 [L,R=301]
RewriteCond %{HTTP_HOST} ^ipstenu\.org
RewriteRule ^wp-content/blogs.dir/1/files/(.*)$ https://ipstenu.org/wp-content/uploads/sites/1/$1 [L,R=301]
RewriteCond %{HTTP_HOST} ^ipstenu\.org
RewriteRule ^(.*)favicon.(ico|png)$ /code/images/favicons/ipstenu.ico [L,R=301]
[NB: This sort of thing is duplicated for other domains which also had things moved around]
</IfModule>
I cut out a couple of sections, where what I did with the ‘Only redirect ipstenu.org’ stuff is repeated for each site. I built much of that after reading my 404 logs and determining what needed to be redirected. Everything is commented and in logical sections, so I can easily find and remember what the heck I was doing.
This is the longest section, the stuff under # Ipstenu Moves
and such, because they’re accounting for files that moved a million years ago. But the moves section is pretty straightforward too, as you can see where things went. I try to keep it as compact as I can. Sometimes I go through and make them more and more efficient, as I learn new tricks.
What used to be here?
I used to include the 5G Blacklist 2013 and/or the 2013 User Agent Blacklist (or whatever the current versions are), but now I don’t have it on all my sites because of the work I’ve been putting in on my firewall and ModSecurity instead. Every once in a while, someone tells me I’m putting too much work on Apache to handle the hackers and spammers, and I generally reply “Better Apache than WordPress.”
Regularly, I go through my .htaccess and see what I can push over to ModSec. I also trim down my PageSpeed rules into things that work on most sites, things that only work on this site, and things that work for everything. This is why there’s no blacklisting here, it’s all handled by my firewall and mod_security and that’s that. I like to take the load off of apache and .htaccess and PHP and make the server do the work.
Why not nginx?
Per site configuration is still a pill. No, really that’s it. It’s a hassle to re-do everything I have in htaccess, I can’t just toss a .nginx file in there on the fly without restarting nginx, which means for shared hosts it’s just not gonna happen. Sucks. For managed hosting, where you don’t allow users to make those changes, sure, it’s great. But that’s not my use case. I may use it for a Varnish in front server one day, after I rebuild everything from scratch.