A compromised PHP website can contain more than a defaced homepage or visible spam links. Hidden backdoors can let attackers return after the obvious damage is removed. The recovery process applies to sites on shared hosting, VPS and dedicated servers. If malicious files reappear after cleanup, investigate remaining backdoors, stolen credentials and unpatched vulnerabilities rather than assuming the visible damage was the whole compromise.
This article walks through a practical, no-drama process to clean hacked PHP websites properly. We will focus on how to detect backdoors, how to scan code over FTP or SSH, and when it is safer to migrate to a fresh hosting account or server instead of trying to patch a badly compromised environment. The steps apply to WordPress, Laravel, custom PHP apps and most other PHP-based systems.
Why Hacked PHP Sites Are Hard to Clean Properly
PHP malware rarely behaves like a single obvious file named “virus.php” sitting in your web root. Attackers usually:
- Plant multiple small backdoors in different folders
- Inject code into existing files (themes, plugins, libraries, index.php, configuration files)
- Abuse writable directories (like uploads or cache) to store arbitrary PHP shells
- Modify the database to inject spam links or malicious JavaScript
Additional risk factors to check in shared hosting and VPS setups include:
- Weak file permissions (world-writable directories, 777 folders)
- Outdated PHP versions and libraries
- Multiple sites sharing the same user account
All of this makes a hacked PHP site a bit like an apartment with several hidden spare keys left under different doormats. You can change the main lock, but if the spare keys remain, intruders will keep coming back. That is why a proper cleanup combines:
- Immediate containment and backups
- Systematic search for backdoors in files and database
- Security hardening of PHP, file permissions and web server
- Often, migration to a fresh hosting account or VPS
First Response: Contain the Incident and Collect Evidence
Before touching files or deleting anything, take a methodical first-response approach. You want to:
1. Limit damage and stop automated abuse
- Secure account access from a trusted device. Change hosting-panel and remote-access passwords, revoke compromised SSH and deployment keys, and invalidate active sessions where supported. Rotate database, CMS and application secrets as part of recovery; do not leave final replacement secrets exposed to a still-compromised application.
- Restrict public access to the compromised application through a trusted hosting or web-server control, serving a static temporary 503 page where possible. CMS maintenance mode alone may leave backdoor URLs accessible. If spam or other abuse continues, ask the host to isolate the account or isolate the server while retaining administrative access for investigation.
- If you use a reverse proxy or CDN like Cloudflare, make sure basic WAF and rate limiting rules are enabled. Our article on Cloudflare security settings with WAF, rate limiting and bot protection shows practical rules we recommend. These controls supplement isolation; they do not remove existing backdoors.
2. Take full backups before cleaning
Even if the site is hacked, you must keep a snapshot of the compromised state for two reasons:
- Forensic analysis later (where did the attack start?)
- Ability to recover accidentally removed data after reviewing it for malicious content, rather than restoring the compromised snapshot wholesale
At minimum, take:
- A complete copy of all web files (public_html or application directory)
- Database dumps for all related databases
- Configuration files outside the web root (.env, config directories, etc.)
- Available web access/error, authentication and application logs before they rotate; request host-held logs promptly if needed
If your site is on cPanel, you can follow our full cPanel backup and restore guide to capture an account backup before you start cleaning. Label incident backups as compromised, restrict access and keep an untouched copy outside the affected account and any publicly served directory. They can contain malware and secrets. Longer term, implement a robust plan like the 3-2-1 backup strategy with automated backups on cPanel, Plesk and VPS to retain multiple recovery points. Check candidate backups for signs of compromise and test restoration in isolation; a backup strategy alone does not guarantee clean restore points.
3. Write down basic incident details
Even a simple text document helps later when you review security:
- When did you first notice the hack?
- What symptoms appeared (defacement, redirects, spam emails, high CPU)?
- Did your registrar, hosting provider or browser show phishing/malware warnings?
- What software versions are you running (PHP, CMS, key plugins)?
These details help you narrow down which vulnerability was most likely exploited.
Detecting PHP Backdoors and Hidden Malware
Once you have backups, the main battle is discovering every backdoor. Attackers usually hide them in writable folders, obscure filenames, or inside legitimate files.
Typical backdoor patterns in PHP code
PHP backdoors can use risky functions and patterns such as those below. This list is not exhaustive, and a search with no matches does not establish that the site is clean:
- Dynamic execution and legacy indicators:
eval()can execute supplied PHP code. String-code evaluation byassert()was deprecated in PHP 7.2 and removed in PHP 8.0; assert itself remains available for assertions.create_function()was deprecated in PHP 7.2 and removed in PHP 8.0.preg_replace()stopped supporting the/eevaluation modifier in PHP 7.0. Match these legacy indicators against the PHP version involved; their presence alone does not prove a working backdoor on the current runtime. - System commands:
system(),exec(),passthru(),shell_exec(),popen(). - Obfuscation helpers:
base64_decode(),gzinflate(),str_rot13(), nested multiple times. - Suspicious superglobal access: building PHP code from
$_POST,$_GET,$_COOKIE, then passing it toeval, or to string-evaluatingasserton PHP versions before 8.0. Inspect the actual data flow and runtime; do not treat modern assert as a string-code evaluator. - Random filenames and paths: small PHP files with random names inside
uploads,cacheor temporary directories.
Simplified backdoor pattern for recognition only: it executes code supplied through a POST request. Do not deploy or run it.
<?php
if (isset($_POST['cmd'])) {
eval(base64_decode($_POST['cmd']));
}
?>Real-world malware usually wraps similar logic with obfuscation, huge useless string arrays, and confusing variable names to hide from quick inspection.
Searching for backdoors over SSH
If your hosting plan or VPS gives you SSH access, you have a huge advantage. You can scan the filesystem quickly using command-line tools. Some useful approaches:
1. Search for dangerous functions
From the site root directory:
Prefer a separate inspection copy and confirm the working directory and authorized scope first. These examples search file contents beyond PHP files. With GNU grep, -R follows symbolic links; check that linked paths do not lead beyond the intended analysis scope. Review permission errors and incomplete output as limitations of the search.
grep -R "eval(" -n .
grep -R "base64_decode" -n .
grep -R "gzinflate" -n .
grep -R "shell_exec" -n .Then review the results manually. These functions can appear in legitimate code, so a match is a lead, not proof of malware. Literal searches also miss altered spacing, indirect calls and other backdoor patterns. Compare suspect files with trusted source code. Focus on files that:
- Are absent from the trusted release or appear where the application should not store executable code, such as an uploads directory intended only for media
- Have random-looking names (e.g.
k9s8d.php) - Are very small yet contain heavy obfuscation and long encoded strings
2. List recently modified files
Attackers often touch many files in a short window. Look at the last few days or weeks:
find . -type f -mtime -7 -name "*.php" -ls
This lists files whose names end in .php and whose modification times fall within the last seven days. Compare the timeline against when you first noticed symptoms. Older timestamps do not establish safety: attackers can alter them, and malicious code may use other filenames.
3. Use command-line malware scanners (if allowed)
On VPS and dedicated servers, you can install open-source antivirus or malware scanners to assist manual review. They are not perfect, but they catch many known signatures and suspicious patterns. Run them against the document root and uploads directories. Combine their findings with your own grep and diff analysis.
Scanning when you only have FTP access
If your shared hosting account offers FTP or SFTP but no SSH access, you can still inspect a downloaded copy with a few extra steps:
- Use SFTP instead of unencrypted FTP if your account supports it. Unencrypted FTP exposes credentials in transit; FTPS is a separate encrypted option if SFTP is unavailable. We explain why this matters in our article on switching from FTP to SFTP for secure file transfer.
- Download a full copy of your site, including hidden files, to an isolated analysis environment. Keep it outside any served web directory and do not execute the downloaded PHP or load the site in a browser.
- Scan locally with your desktop antivirus and any specialized web malware scanner you trust.
- Search the code with your local tools: text editor search,
grepor IDE inspections for functions likeeval,base64_decode,gzinflate, etc. - Use diff tools to compare your downloaded code with an official copy of the same CMS or framework version. Compare extensions and dependencies against their matching trusted releases too; account for documented local changes.
Searching and comparison can identify useful leads, but FTP access may not expose account-level persistence, logs or other sites. Ask the host about scheduled tasks, SSH keys, panel accounts, mail forwarding and files outside the visible web root. A scan with no findings does not establish that all backdoors have been removed.
Cleaning and Hardening in Place vs Migrating to a New Hosting Account
Choose between cleanup in the existing environment and a clean rebuild according to the verified scope of compromise and whether the host can still be trusted. A new directory or account on a compromised operating system does not by itself restore trust.
When cleanup in the current environment may be appropriate
Consider the existing account only after assessing these conditions:
- The entry route and affected resources can be investigated; a first known incident alone is not evidence of limited scope.
- You or the host can inspect relevant files, ownership, accounts, scheduled tasks and available logs.
- Other sites and services sharing the user or server have been included in the assessment; there is no unresolved evidence of broader access.
- You can replace affected code from trusted sources, revoke compromised access and correct permissions without relying on the suspect application.
Before reopening public access, address the entry route, update supported components, rotate affected credentials and review PHP settings, ownership, permissions, WAF and firewall rules. Test a recovery plan and monitor after reopening.
When you should migrate to a fresh account or VPS
Rebuild on a trusted environment when the current one cannot be adequately assessed or recovered. Reasons to investigate this option include:
- Multiple sites under the same user are compromised.
- Unexpected system users, scheduled tasks or SSH keys suggest broader persistence.
- Ownership and permissions cannot be reliably reconstructed.
- You and the host cannot obtain enough access or logs to assess the affected environment.
- The stack is unsupported or cannot be brought to a maintainable state. Extra capacity alone does not remove a compromise.
Prepare a supported, isolated hosting account or server and migrate only reviewed data and trusted application code. If the operating system or privileged account was compromised, coordinate a host rebuild or use a separate trusted server; another account on the same suspect host may be insufficient. Verify isolation and service responsibilities for the hosting plan you choose.
Safe File-Level Cleanup: Step-by-Step
Use a documented file inventory and keep the untouched incident snapshot before making changes. Work on an isolated copy or new installation so that cleanup steps do not destroy evidence or publish unreviewed code.
1. Build a clean baseline
For WordPress, Laravel, PrestaShop or another application, obtain the exact installed release and relevant locale from its official or otherwise verified source for comparison. The same major version is not precise enough. For recovery, separately select a supported, compatible release and review required upgrades. The baseline covers:
- Core files that must match exactly
- Default themes or skeleton app files
- Standard directory structure
Then:
- Over SSH: use
diff -ror an equivalent tool on the inspection copy and matching trusted release. - With downloaded files: compare folders using a diff-capable file manager or editor without executing the suspect application.
Review additional and modified files against documented customizations and build output. Include hidden files, configuration outside the web root and every site that shares the affected account. WordPress core checksums can support release comparison; a matching result covers only the checked scope and does not prove that plugins, uploads, the database or the account are clean.
2. Replace core and vendor files from trusted sources
Replace core and dependency packages from verified sources where possible. Preserve evidence and custom data first, and plan the replacement on an isolated installation:
- Build a clean directory with the required core files, such as
wp-adminandwp-includes, or verified Composer dependencies invendor. Overwriting existing files can leave attacker-added files behind. - Use a trusted release or reviewed source commit and dependency lock file. Assess repository and CI/CD access too; do not execute installer scripts or Composer plugins from a suspect checkout merely to rebuild it.
- Review configuration such as
wp-config.php,.env,.htaccessand.user.ini, including unexpected includes and settings. Preserve required configuration deliberately and install fresh secrets after the environment is trusted.
For WordPress in particular, we go into more detail in our article on what to do if your WordPress site keeps getting hacked, but the same principles apply to most PHP applications.
3. Clean uploads, custom code and themes/plugins
Uploads directories need particular attention because the application often writes there. Their intended content and execution policy must be explicit; an image or PDF extension does not by itself make a file safe.
- Inventory unexpected executable files in uploads and similar directories. Compare each with the intended application behavior; filenames and extensions are only leads.
- Preserve and quarantine confirmed malicious files outside served paths before removing their active copies. Do not bulk-delete everything named PHP or test by executing suspect files.
- Review custom code, themes and modules, or redeploy a verified release from a trusted repository. Include WordPress mu-plugins and drop-ins where present.
- Update or remove obsolete components after preserving needed data. Patch the entry route and check that replacement code works in the isolated environment.
Configure uploads so they cannot be executed as PHP. The mechanism depends on the actual web server and PHP handler: Apache may use permitted .htaccess rules, while Nginx requires server configuration. Verify the effective behavior and prevent alternate executable handlers from bypassing it. Combine this with Linux file permissions and ownership suited to your hosting environment.
Database Inspection and Cleanup
Even if all files are clean, attackers may have injected spam or scripts into the database. Typical signs include:
- Strange content added to posts, products or pages (hidden links, iframes)
- Unknown admin users or users with elevated roles
- Malicious code in settings fields that get printed on every page
Steps:
- Back up the database before editing and keep a restricted incident copy.
- Search on an isolated inspection copy with read-only database access or a trusted administration tool. The following examples use fictional table and column names; adapt them to your schema:
SELECT * FROM posts_table WHERE content LIKE '%<iframe%'; SELECT * FROM posts_table WHERE content LIKE '%base64_decode%'; SELECT * FROM options_table WHERE option_value LIKE '%<script%';
The SELECT examples do not change data. They search only the displayed columns using LIKE patterns: % matches zero or more characters and _ matches one character. The unescaped underscore in base64_decode can also match text such as base64Xdecode. Legitimate embeds or educational code may match, while other malicious content may not. Inspect each finding in context, including unfamiliar domains and encoded values. Review settings and custom fields too. Preserve a backup and use application-aware, verified record edits; broad replacements can corrupt legitimate content or serialized settings.
- Review accounts and roles. Confirm account ownership and recent actions. Disable suspicious access while preserving evidence; check content ownership before deletion. Revoke sessions, application passwords and related tokens as appropriate.
- Check configuration tables for unexpected code in headers, footers or widgets. High option size or database bloat alone is not proof of compromise.
For WordPress specifically, database bloat and strange options in wp_options can also hurt performance. Our guide on WordPress database optimization and cleaning up wp_options/autoload bloat shows how to safely inspect and tidy up the most sensitive tables.
Planning and Executing a Safe Migration
If you decide to migrate to a new hosting account, VPS, dedicated server or colocation setup, treat the move as both a cleanup and an upgrade opportunity. The goal is to leave all backdoors behind, not to copy them faithfully to a fresh server.
1. Prepare the new environment securely
On the new hosting or server:
- Use a supported, patched PHP version and compatible extensions. Review optional function restrictions against application needs;
disable_functionsis not a security boundary and cannot replace patching or isolation. - Set file ownership and least required read/write permissions. Avoid world-writable code and keep secret-bearing configuration inaccessible from the web.
- Configure firewall rules and protect management access. For VPS and dedicated servers, see our guide to securing a VPS server.
- Configure valid TLS before public traffic. Test suitable HTTP security headers such as CSP and Referrer-Policy; stage HSTS only after confirming HTTPS works for its intended scope. Further background is available in the DCHost blog.
2. Copy, scan and verify before DNS cutover
For a clean migration flow:
- Copy only reviewed data and trusted code:
- Fresh core/framework files from verified releases or a reviewed build
- Reviewed custom themes, modules and dependencies
- Uploads and media after content and malware inspection
- An inspected database dump with account, role, setting and content checks completed
- Inspect again before exposure. Combine source comparison, scoped text searches, an appropriate scanner and manual review. Keep the new application restricted while suspicious data is being imported or examined.
- Test privately. A hosts-file override or temporary name can support testing, but does not itself restrict access. Verify TLS, logins, forms, checkout and integrations. Prevent test emails, live payments and unintended outgoing webhooks.
- Plan a consistent cutover. Pause writes or use an application-appropriate synchronization process during final data transfer. Reducing TTL early can help, but does not shorten the lifetime of responses already cached or guarantee no downtime. See TTL strategies for migration planning. Keep the old compromised application inaccessible during DNS propagation.
3. Post-migration security checklist
Complete credential replacement before reopening the application, then verify these controls during and after the DNS cutover:
- Rotate affected CMS, database, panel and remote-access credentials from a trusted device. Revoke compromised SSH/deploy keys, API tokens and sessions; replace new secrets if they were exposed to the suspect environment.
- Remove unnecessary test accounts and demo data after preserving anything required for the incident record.
- Verify that directory listing is disabled and uploads cannot execute PHP through the configured handlers.
- Monitor availability, application errors, file changes and account activity, with a named responder for alerts.
- Revoke old access and keep the old application isolated. Retain restricted evidence for the required period before decommissioning the old account; never use the infected snapshot as a production rollback.
Long-Term Hardening to Avoid Repeat Hacks
If the entry route or unauthorized access remains, the site can be compromised again. Use the incident to improve ongoing controls and document who maintains them:
- Keep software supported and patched: CMS, plugins, themes, frameworks, PHP and the database. Test compatibility and retain a clean recovery path.
- Review ownership and permissions: 644 for files and 755 for directories are examples for some setups, not universal safe settings. They leave the owner able to write; secrets may require tighter permissions. See Linux file permissions 644, 755 and 777.
- Separate sites and access: avoid unnecessary shared users or credentials and verify the isolation that your hosting model provides.
- Use a WAF as an additional layer: test suitable ModSecurity or CDN rules. It does not remove existing backdoors or replace patching and credential recovery.
- Strengthen authentication: unique strong passwords, 2FA where available, controlled management access and prompt revocation of obsolete keys/tokens. Replace compromised credentials; arbitrary periodic password changes are not a substitute for these controls.
- Monitor logs and integrity: review suspicious requests, successful account access, unexpected tasks and file changes. Signals require investigation and do not guarantee early detection.
- Keep independent, versioned backups: choose frequency according to acceptable data loss and test restoration in isolation. A backup older than the first visible symptom may still contain the compromise.
For WordPress sites in particular, we have a practical checklist in our guide on WordPress security on shared hosting using plugins, WAF, 2FA and backups, but the main principles apply to most PHP applications.
Bringing Your Hacked PHP Site Back Under Control
Contain the incident and preserve evidence, then investigate files, the database, account access and persistence. Use grep, diff, scanners and manual review within their limits. Replace core and dependency code from verified sources, address the entry route and validate recovery in isolation. If the host cannot be trusted, rebuild on a trusted environment and migrate only reviewed data.
Before reopening, verify application behavior, permissions and replacement credentials. Keep a clean rollback route, independent backups and ongoing monitoring with clear responsibility. When evaluating a DCHost hosting plan, confirm the available isolation, log access, backup and security-management scope; do not assume every plan includes incident cleanup or managed monitoring.





