In Part 1 of this series, we explored nine useful .htaccess techniques for improving WordPress security and performance.
We covered WordPress permalinks, administrator access restrictions, sensitive file protection, browser caching, and PHP configuration.
In Part 2, we will continue with nine additional tips that focus on Apache security, website caching, URL redirects, directory management, and server configuration.
The .htaccess file is particularly useful because it allows administrators to control certain Apache behaviors without modifying the main server configuration.
However, not every directive is supported by every hosting environment.
The examples in this tutorial are designed primarily for Apache HTTP Server 2.4.
Before applying them, back up your existing .htaccess file and confirm that your hosting provider supports the required Apache modules.
Tip 10. Implement a Website Caching Scheme Using .htaccess
Caching is one of the most effective ways to reduce repeated network transfers and improve the loading experience of a WordPress website.
When visitors open a webpage, their browsers download images, stylesheets, JavaScript files, and other resources.
Without appropriate caching, some resources may be downloaded repeatedly.
Apache provides several mechanisms for controlling HTTP caching, including the mod_expires and mod_headers modules.
In Part 1, we introduced browser caching using ExpiresByType. We can extend that approach by setting Cache-Control headers for selected static files.
For example:
<IfModule mod_headers.c>
<FilesMatch "(?i)\.(jpg|jpeg|png|gif|webp|avif)$">
Header set Cache-Control "public, max-age=2592000"
</FilesMatch>
</IfModule>
This rule tells browsers and eligible intermediate caches that matching image responses may remain fresh for 2,592,000 seconds, or 30 days.
The public directive permits storage by shared caches when other HTTP caching conditions allow it.
The max-age directive specifies the freshness lifetime.
For JavaScript and CSS files, a shorter caching period may be appropriate:
<IfModule mod_headers.c>
<FilesMatch "(?i)\.(css|js)$">
Header set Cache-Control "public, max-age=604800"
</FilesMatch>
</IfModule>
This configuration sets a seven-day freshness lifetime.
Longer cache lifetimes are appropriate when static resources use versioned URLs, so changes can be published under a new filename or URL.
Be careful when combining these rules with WordPress caching plugins, Cloudflare, or hosting-provider caching.
Avoid generating duplicate or contradictory Cache-Control headers.
Also remember that browser caching is different from WordPress page caching.
Browser caching reduces repeated downloads, while page caching can reduce the need for PHP and database processing.
Tip 11. Create Permanent 301 Redirects
Website addresses sometimes change because of domain migrations, permalink updates, or website restructuring.
When a webpage moves permanently, an HTTP 301 redirect tells browsers and search engines that the resource has a new address.
Suppose your old webpage is:
https://example.com/old-page.html
And the new address is:
https://example.com/new-page.html
You can create a permanent redirect using Apache’s Redirect directive:
Redirect 301 /old-page.html https://example.com/new-page.html
This directive is provided by Apache’s mod_alias module.
When visitors request the old address, Apache returns an HTTP 301 Moved Permanently response with the destination URL.
The browser then requests the new location.
Permanent redirects are especially useful when changing WordPress permalink structures, merging older articles, or moving content to a new website.
However, avoid redirect chains. For example, redirecting page A to page B and then to page C creates unnecessary requests.
Whenever possible, redirect the original URL directly to its final destination.
Also avoid redirecting every deleted page to the homepage. If no relevant replacement exists, returning a proper HTTP 404 or 410 response may be more appropriate.
If your website already uses WordPress redirect plugins or CDN redirect rules, coordinate those configurations to prevent conflicts.
Tip 12. Protect php.ini and Other Sensitive Configuration Files
PHP and WordPress installations may contain configuration files that should never be publicly accessible.
Examples include php.ini, .user.ini, .env, and other application configuration files.
These files may contain information about PHP settings, environment variables, or application behavior.
To block access to selected configuration filenames, add:
<FilesMatch "(?i)^(php\.ini|\.user\.ini|\.env(?:\..*)?)$">
Require all denied
</FilesMatch>
This configuration prevents direct HTTP requests for the specified files.
The FilesMatch directive uses a regular expression to identify matching filenames.
The Require all denied directive tells Apache to deny access.
The rule uses case-insensitive matching and also covers filenames beginning with .env followed by an additional suffix.
However, this is only a protective measure against HTTP access.
It does not prevent another compromised application or process from reading files through the filesystem.
Sensitive configuration files should use appropriate permissions and, whenever possible, be stored outside the public website directory.
Do not rely on filename filtering as the only protection for database credentials, API keys, or other secrets.
Tip 13. Prevent Direct Access to PHP Include Files
PHP applications commonly use include and require statements to load shared libraries, functions, and configuration files.
For example, a website may contain a private application library inside an includes directory.
Such files may be intended only for internal use by other PHP scripts.
Allowing visitors to request internal PHP files directly can create unnecessary exposure, particularly if those files were not designed to operate independently.
For a custom application directory where no PHP file needs direct HTTP access, create a separate .htaccess file inside that directory.
For example:
public_html/private-includes/.htaccess
Add:
<FilesMatch "(?i)\.(php|phtml)$">
Require all denied
</FilesMatch>
This denies direct HTTP access to matching PHP files.
PHP scripts elsewhere on the server can still load these files internally when filesystem permissions allow it.
For example, an application can use require to load a library without exposing that library as a public URL.
An even better design is to store private application libraries outside the document root whenever possible.
For WordPress, be careful when applying similar restrictions to wp-includes or plugin directories.
Some PHP endpoints are intentionally accessible, and blocking them may break legitimate functionality.
Therefore, apply these rules only to directories whose purpose and contents you understand.
Tip 14. Disable Apache Server Signature
When Apache generates an error page, it may include information identifying the server software.
Depending on the configuration, the response may display the Apache version, server hostname, or other information.
Although revealing a server version does not automatically create a vulnerability, reducing unnecessary server information can be a useful hardening measure.
Apache provides the ServerSignature directive.
To disable the server signature on server-generated pages, add:
ServerSignature Off
This directive prevents Apache from displaying its server signature in supported server-generated documents.
However, ServerSignature is not the same as ServerTokens.
The ServerTokens directive controls the information Apache exposes through its Server HTTP response header and certain other outputs.
For example, administrators with access to the main Apache configuration may use:
ServerTokens Prod
This reduces the Apache product information exposed by the server.
Unlike ServerSignature, ServerTokens cannot normally be configured in .htaccess. It requires server-level configuration.
If you use shared hosting, your hosting provider controls this setting.
Keep in mind that hiding server information does not fix vulnerabilities.
Regular updates and secure configuration are more important than concealing version numbers.
Tip 15. Configure Your Website Timezone Correctly
Websites often need consistent timezone settings for timestamps, scheduled tasks, logs, and date-related operations.
For example, an international website may serve visitors across several timezones while its server operates in UTC.
PHP and WordPress provide mechanisms for handling timezone configuration.
Some older tutorials suggest adding environment variables to .htaccess:
SetEnv TZ America/Vancouver
This sets an Apache request environment variable when mod_env is available.
However, it does not reliably configure the PHP default timezone across different PHP execution environments.
Modern hosting environments commonly use PHP-FPM, and the timezone environment or PHP configuration may be managed independently.
For PHP applications, the preferred configuration is the PHP date.timezone directive.
For example, in an appropriate PHP configuration file:
date.timezone = America/Vancouver
If your hosting environment supports .user.ini files, this directive may be configured there, subject to PHP configuration restrictions.
WordPress provides its own timezone setting through the administration dashboard.
Go to Settings, then General, and locate the Timezone option.
For WordPress websites, selecting the correct geographical timezone in WordPress is generally preferable to relying on an Apache environment variable.
This helps WordPress handle scheduled publishing and date display according to the site’s configured timezone.
The server’s operating system timezone, PHP timezone, and WordPress timezone are separate settings and do not necessarily need to match.
Tip 16. Prevent Directory Listings
Directory listing is an Apache feature that displays the contents of a directory when no suitable index file is present.
For example, a visitor requesting a directory URL might see a list of filenames and subdirectories if directory indexing is enabled.
This can reveal unnecessary information about your website.
To disable directory listing, add:
Options -Indexes
This tells Apache not to generate automatic directory listings.
If a visitor requests a directory without an index file, Apache generally returns HTTP 403 Forbidden instead of displaying the file list.
For WordPress websites, disabling directory listing is usually a sensible security measure.
However, it does not prevent visitors from downloading individual files when they already know the exact public URLs.
Protect confidential resources separately.
Apache also supports FancyIndexing through mod_autoindex, which can produce more detailed directory listings.
Although this feature may be useful for public software repositories or file distribution directories, it is rarely necessary for ordinary WordPress websites.
For most WordPress installations, keeping directory listing disabled is the simpler choice.
Tip 17. Change the Website Root Directory Using URL Rewriting
Sometimes website administrators want visitors to access content stored inside a subdirectory while keeping a clean public URL.
For example, suppose a custom PHP website stores its public pages inside:
public_html/site/
But visitors should continue using:
Apache rewrite rules can internally map incoming requests to that subdirectory.
For a carefully controlled custom application, one possible configuration is:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_URI} !^/site(?:/|$)
RewriteRule ^(.*)$ site/$1 [END]
</IfModule>
This internally rewrites eligible requests to resources under the site directory.
The END flag, supported by Apache 2.4, stops further rewrite processing for the request.
However, URL rewriting does not actually change Apache’s DocumentRoot setting.
The DocumentRoot directive determines the filesystem directory used as the website’s document root, and it must be configured at the appropriate server or virtual-host level.
Changing the public document root is therefore different from internally rewriting URLs.
For WordPress, this distinction is especially important.
WordPress has its own Site Address, WordPress Address, permalink rules, and installation-directory behavior.
Adding a generic root-directory rewrite rule to an existing WordPress installation can cause redirect loops, missing assets, or incorrect administrative URLs.
If you want to move WordPress into a subdirectory, use the official WordPress procedure for giving WordPress its own directory.
If your goal is to change the actual website document root, use your hosting control panel or contact your hosting provider.
Do not use a simple .htaccess rewrite rule as a replacement for properly configuring the web server’s DocumentRoot.
Tip 18. Prevent PHP and Perl Scripts from Running in Image Directories
Image directories should normally contain static media files rather than executable server-side scripts.
Attackers sometimes exploit vulnerable upload features to place malicious scripts inside directories intended for images and other media.
If a web server executes those uploaded files, attackers may gain unauthorized control over parts of the website.
One defensive measure is to restrict direct HTTP access to executable file extensions inside media directories.
For example, create a separate .htaccess file inside:
public_html/images/
Add:
<FilesMatch "(?i)\.(php|php[0-9]+|phtml|phar|pl|cgi)$">
Require all denied
</FilesMatch>
This denies direct requests for PHP-related, Perl, and CGI script filenames matching the listed extensions.
Images such as JPG, PNG, GIF, and WebP files remain accessible.
However, blocking filenames through .htaccess is only one security layer.
The strongest protection is to configure the server so user-writable media directories cannot execute scripts.
Administrators should also disable unnecessary CGI execution and restrict executable handlers in upload locations through appropriate server configuration.
For WordPress websites, the same principle applies to the wp-content/uploads directory.
If you already implemented the PHP-file protection rule in Part 1, you do not need to duplicate an identical rule in the same directory.
Also remember that extension-based blocking cannot prevent every upload vulnerability.
Applications must still validate uploads, enforce authorization, and avoid trusting user-supplied file types or filenames.
Testing Your .htaccess Changes
After applying the rules in this tutorial, verify that your website continues to work normally.
Test the homepage, WordPress administration area, images, CSS, JavaScript, and any plugins that depend on PHP endpoints.
You can use curl to inspect HTTP headers.
For example:
curl -I https://example.com/
To inspect a permanent redirect:
curl -I https://example.com/old-page.html
Look for the HTTP 301 status and the Location response header.
To inspect cache directives:
curl -I https://example.com/wp-content/uploads/example.jpg
Look for Cache-Control and Expires headers.
For protected configuration files, verify that direct requests are denied.
If Apache returns an HTTP 500 Internal Server Error after a change, restore your backup and inspect the error logs.
A configuration may fail because a directive is unsupported in .htaccess, a required module is unavailable, or the hosting provider has restricted the directive.
Do not apply all the examples blindly. Choose the configurations relevant to your particular WordPress environment.
Conclusion
The .htaccess file offers WordPress administrators useful tools for controlling Apache server behavior.
In Part 2, we covered website caching, permanent redirects, sensitive configuration files, PHP include protection, server signatures, timezone configuration, directory listing, URL rewriting, and script execution restrictions.
Some of these techniques provide additional security, while others improve website management and resource delivery.
Modern Apache and PHP environments also require administrators to distinguish between settings that belong in .htaccess and those that must be configured through PHP-FPM, a hosting control panel, or the main web server configuration.
The most effective approach is to apply narrowly targeted rules, understand their limitations, and test their interaction with WordPress and any CDN or caching services.
In Part 3, we will complete the series with nine additional tips covering HTTPS redirects, default directory indexes, unwanted bots, image hotlink protection, spam referrers, custom error pages, advanced redirects, media downloads, and suspicious URL character filtering.
