How I Lock Down a PHP Website After It Gets Hacked: My Actual Server Hardening Checklist

Getting a website hacked changes the way you think about web development. Before it happens, security can feel like something that belongs in a separate category from design, performance, SEO, and functionality. After it happens, you realize that security touches almost every part of the application. File uploads, database permissions, login sessions, error messages, server configuration, backups, and even innocent-looking contact forms can become attack surfaces if they are not handled correctly.


One of the biggest mistakes developers make after a compromise is trying to fix only the visible damage. They delete the spam pages, change the administrator password, update a few plugins, and assume the problem has been solved. That may remove the symptom, but it does not necessarily remove the vulnerability that allowed the attacker to get inside in the first place. If the original entry point still exists, the attacker can simply return.

I take a different approach. Once a website has been compromised, I assume that everything connected to that application needs to be reviewed. That includes the public files, the database, server configuration, scheduled jobs, administrator accounts, file permissions, API keys, upload directories, and authentication system. The goal is not merely to clean the website. The goal is to rebuild trust in the environment.

This article explains the security model I use when hardening a custom PHP and MySQL website. Some of these techniques apply to WordPress, Laravel, Symfony, and other platforms as well, but the examples here focus primarily on traditional PHP applications running on Apache.

Start With the Correct Assumption

The first thing I assume after discovering a compromise is that the attacker may have obtained more access than is immediately visible. Finding one malicious PHP file does not mean there is only one malicious PHP file. Attackers commonly create secondary access points so that deleting the original malware does not lock them out.

For example, an attacker may upload a web shell into an image directory, inject PHP into a legitimate application file, create a hidden administrator account, modify a scheduled task, or insert malicious JavaScript into the database. They may also create files with innocent-looking names such as cache.php, class.api.php, or system-helper.php so that the malware blends in with legitimate application code.

Because of this, simply sorting a directory by filename is not enough. I want to know which files were modified recently, which files contain executable PHP, which directories unexpectedly contain scripts, and whether any files differ from known clean versions.

One useful Linux command is:

find /var/www/html -type f -mtime -14 -print

This lists files modified during the previous fourteen days. It does not prove that those files are malicious, but it gives you a starting point when investigating a recent compromise.

You can also search for PHP files inside directories that normally should contain only static content:

find /var/www/html/uploads -type f \( -name "*.php" -o -name "*.phtml" -o -name "*.phar" \)

If your upload directory is supposed to contain photographs, PDFs, and user documents, discovering executable PHP in that directory deserves immediate investigation.

Rotate Every Credential That Could Have Been Exposed

Changing the website administrator password is not enough. If an attacker obtained access to the server or application files, they may have been able to read database passwords, API credentials, SMTP passwords, Stripe secrets, session keys, and other authentication material.

I therefore treat credential rotation as part of the cleanup process. Database passwords should be changed. Hosting control panel passwords should be changed. SSH credentials should be reviewed. API keys should be regenerated where appropriate. Email credentials should also be replaced if the application stores them.

Environment configuration deserves special attention. Many PHP applications store sensitive values inside .env files or configuration files. A typical configuration might contain something similar to this:

DB_HOST=localhost
DB_DATABASE=production
DB_USERNAME=website_user
DB_PASSWORD=replace_this_password

STRIPE_SECRET_KEY=replace_this_key
OPENAI_API_KEY=replace_this_key

These files should never be accessible through the public web server. If someone can visit https://example.com/.env and download that file, the application has a serious configuration problem.

On Apache, I explicitly block access to sensitive files.

<FilesMatch "^(\.env|\.git|composer\.(json|lock)|package(-lock)?\.json)$">
    Require all denied
</FilesMatch>

This does not replace correct directory structure, but it gives you another layer of protection.

Keep Sensitive Configuration Outside the Public Web Root

One of the strongest architectural improvements you can make is separating application secrets from publicly accessible files. Ideally, the public web directory should contain only the files that actually need to be reachable from a browser.

Instead of storing database credentials inside something like:

/public_html/config.php

I prefer something closer to:

/home/account/private/config.php
/home/account/public_html/index.php

The application can include the private configuration file internally, but Apache cannot serve it directly because it sits outside the document root.

This matters because server misconfiguration happens. A PHP handler can fail. Apache configuration can change. A backup file can accidentally become downloadable. Keeping secrets outside the web-accessible directory reduces the number of ways they can leak.

Disable Directory Listings

Directory indexing is another unnecessary source of information disclosure. If a directory does not contain an index file, some Apache configurations may display the contents of that directory. An attacker can use that listing to discover filenames, backup archives, scripts, and internal directory structures.

I disable directory indexes globally for the application.

Options -Indexes

There is usually no reason for a normal web application to expose its directory structure to anonymous visitors.