I run a plain PHP site on shared hosting. No Node, no build step, no server I control. That means one small file does a lot of the security and routing work for me: .htaccess.
It sits in the root of the site, Apache reads it on every request, and two recipes in it are the ones I would never ship a site without. The first forces HTTPS. The second hides my config files from the web.
Forcing HTTPS
Shared hosting usually gives you a free certificate these days, but having one is not the same as using it. Until I added the redirect, the plain http:// version of my site still answered. Search engines could index both versions, and a visitor following an old link could end up on the unencrypted one.
The fix is three lines of mod_rewrite:
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
The condition checks whether the request came in over HTTPS. If it did not, the rule sends the visitor to the same path on https:// with a 301, a permanent redirect. The 301 matters: it tells search engines the secure address is the real one, so the old one stops getting indexed. I put this at the very top of the file, before anything else, so no other rule can answer an insecure request first.
One mistake I made early on: I tested it with a 302 at first and left it there for days. A 302 says "temporary", so nothing upstream treated the secure URL as canonical. If you are sure the certificate works, use the 301 straight away. Test the certificate first, then flip the redirect.
Blocking config files
My site keeps its RapidAPI key in api/config.php, a file the server-side code includes on every request. The browser should never be able to open it directly. If that file ever leaked its contents, anyone could read the key and spend my API quota.
PHP files are usually safe by accident — the server runs them instead of showing the source — but "usually" is not a plan. A misconfigured server, a backup copy saved as config.php.bak, or a stray .env file can all be served as plain text. So I block access outright:
<FilesMatch "\.(php|inc|env|ini|log)$">
<If "%{REQUEST_URI} =~ m#^/api/config#">
Require all denied
</If>
</FilesMatch>
That is the idea, but honestly the version I actually trust is simpler and harder to get wrong. I deny a named file directly:
<Files "config.php">
Require all denied
</Files>
And I do the same for any backup or editor file that might appear in the folder:
<FilesMatch "\.(bak|old|swp|env)$">
Require all denied
</FilesMatch>
The FilesMatch rule is a safety net for the files I forget about. My deploy checklist now includes a step I wish I had written down on day one: after every upload, try to open config.php and a .bak file in the browser. Both should return 403. If either returns 200, the upload stops until it is fixed.
Two more habits that belong in the same file
First, turn off directory listings. If a folder has no index.php, Apache will happily list its contents to anyone who asks:
Options -Indexes
One line, and a whole class of "what is in this folder" questions goes away.
Second, keep the rules tidy. .htaccess is read on every single request, so a long file full of dead rules is a small tax on every visitor. I keep mine short, comment each block with one plain line explaining why it exists, and delete rules the moment they stop being needed.
Why this matters on shared hosting
On a server I controlled, I would put these rules in the main server config and turn .htaccess off entirely — it is faster that way. But shared hosting never gives you that option. The .htaccess file is the only lever you get, and it is a good one: the HTTPS redirect and the config block together take about ten lines, cost nothing, and remove two of the most common ways a small PHP site leaks something it should not.
I learned both the hard way on ramrom.io — the first when I noticed the insecure URL still answering, the second when I realised my key file was only protected by luck. Neither needed a framework, a plugin, or a paid service. Just a small file, read carefully, tested in the browser after every deploy.
Top comments (0)