How to Tell If Your Alpine Linux (or any Linux) WordPress Site Was Hacked
A step-by-step forensic checklist for self-hosted WordPress sites running on Alpine Linux — covering logs, file integrity, database users, network connections, and how to distinguish a system upgrade crash from a real intrusion. My site on Alpine Linux stopped working this morning due to an auto upgrade which I wasn’t expecting. I decided to write this up so that it might help someone.
“Your site just went down. Before you panic, ask the right question: was it a system change — or was it someone else?”
Self-hosted WordPress sites on Alpine Linux are lean, fast, and highly customizable. But when something breaks — a sudden outage, unexpected behavior, or unfamiliar content — many site owners immediately assume the worst: a hack. In reality, most unexpected outages are caused by system package upgrades, not malicious actors. Knowing how to tell the difference quickly and methodically is a core operational skill.
This guide walks through the exact forensic commands to run on an Alpine Linux system hosting WordPress via Podman containers. Every command is tested and real — no theoretical checklists. By the end you will know definitively whether your site was compromised or simply broken by an update. You can adapt the steps here to your version on Linux.
Hack vs. Upgrade Crash — Key Differences
The first question to answer is not “what happened” but “what kind of event is this?” The symptoms of a system upgrade crash and a compromise can look identical from the outside — both result in a site that is down. The difference is in the evidence left behind.
| Signal | Upgrade Crash | Compromise |
|---|---|---|
| Site down after reboot | ✓ Very common | ✓ Possible |
| Container runtime errors in logs | ✓ Typical (cgroups, networking) | ✗ Rare |
| New unknown WordPress admin user | ✗ Never | ✓ Strong indicator |
| Modified PHP files in last 24–48 hrs | ✗ Unlikely | ✓ Strong indicator |
| eval(base64_decode) in PHP files | ✗ Never | ✓ Definitive indicator |
| Unexpected outbound connections | ✗ Unlikely | ✓ Strong indicator |
| Site content defaced or spammy | ✗ Never | ✓ Definitive indicator |
| Package upgrade in system logs | ✓ Present | ✗ Absent |
A Podman v4 to v5 upgrade on Alpine Linux silently changed the default rootless networking from slirp4netns to pasta and the default cgroup handling — causing containers to fail to start after reboot. This is indistinguishable from an outage caused by a hack until you examine the container and system logs.
Check WordPress Files for Malware
The most reliable server-side hack indicator is malicious code injected into PHP files. Attackers typically use obfuscated PHP — base64-encoded payloads wrapped in eval() calls — to establish persistence and execute remote commands. These two grep commands will catch the most common injection patterns.
Scan for Obfuscated Malware
sudo grep -r “eval(base64_decode” /home/testuser/wordpress-podman/wordpress/wp_data
# Scan for gzip-compressed eval payloads (second most common)
sudo grep -r “eval(gzinflate” /home/testuser/wordpress-podman/wordpress/wp_data
# Scan for additional obfuscation patterns
sudo grep -r “eval(str_rot13” /home/testuser/wordpress-podman/wordpress/wp_data
sudo grep -r “preg_replace.*\/e” /home/testuser/wordpress-podman/wordpress/wp_data
# Clean output means no matches — your files are not injected
Find Recently Modified PHP Files
find /home/testuser/wordpress-podman/wordpress/wp_data -name “*.php” -mtime -7
# Files modified in last 24 hours (urgent — investigate immediately)
find /home/testuser/wordpress-podman/wordpress/wp_data -name “*.php” -mtime -1
# Files modified in last 7 days — any file type
find /home/testuser/wordpress-podman/wordpress/wp_data -mtime -7 -type f
Normal results include cache files under wp-content/cache/ and plugin update files such as firewall rule settings. Suspicious results include any .php files in wp-content/uploads/ (uploads should never contain executable PHP), files with random hex names, or any core WordPress files like wp-login.php or wp-config.php showing recent modification timestamps you did not cause.
Audit WordPress Database Users
One of the first things an attacker does after gaining WordPress access is create a hidden admin account for persistent access. This account may have an innocuous name and a personal-looking email address. Auditing your WordPress user table takes seconds and is one of the highest-value checks you can run.
List All WordPress Users and Their Roles
podman exec -it mariadb mysql -u testdbuser -pwordpresspass testdbpass -e \
“SELECT user_login, user_email, user_registered FROM wp_users;”
# List users AND their WordPress roles (administrator, editor, etc.)
podman exec -it mariadb mysql -u testdbuser -pwordpresspass testdbpass -e \
“SELECT user_login, user_email, user_registered, meta_value \
FROM wp_users JOIN wp_usermeta ON wp_users.ID = wp_usermeta.user_id \
WHERE meta_key = ‘wp_capabilities’;”
# Check for recently created users (last 30 days)
podman exec -it mariadb mysql -u testdbuser -pwordpresspass testdbpass -e \
“SELECT user_login, user_email, user_registered FROM wp_users \
WHERE user_registered > DATE_SUB(NOW(), INTERVAL 30 DAY);”
Remove a Suspicious Admin User
podman exec -it mariadb mysql -u testdbuser -pwordpresspass testdbpass -e \
“DELETE FROM wp_users WHERE user_login=’suspicious_user’;”
podman exec -it mariadb mysql -u testdbuser -pwordpresspass testdbpass -e \
“DELETE FROM wp_usermeta WHERE user_id NOT IN (SELECT ID FROM wp_users);”
Any administrator-role user you did not create is a definitive sign of compromise. Do not simply delete the account and move on — if an attacker created an admin account they likely also installed a backdoor plugin or modified a theme file. Run the full malware scan above before considering the incident resolved.
Check SSH Login Attempts
Alpine Linux logs authentication events to /var/log/messages. Reviewing this file tells you who has been trying to log in — and whether any attempt succeeded. Failed logins from internal IPs are usually your own devices with a typo. Failed logins from external IPs are brute force attempts and warrant further investigation.
Review Authentication Logs
sudo grep “Failed password” /var/log/messages | tail -20
# View successful logins
sudo grep “Accepted password\|Accepted publickey” /var/log/messages | tail -20
# Count failed attempts by source IP (spot brute force)
sudo grep “Failed password” /var/log/messages | \
awk ‘{print $(NF-3)}’ | sort | uniq -c | sort -rn | head -10
# View login history
sudo last | head -20
Distinguish Internal vs. External Attempts
arp -a | grep 192.168.0.99
# Check if a suspicious external IP has known reputation
# Visit https://www.abuseipdb.com and enter the IP address
Failed logins from RFC1918 private IP ranges (192.168.x.x, 10.x.x.x, 172.16-31.x.x) are almost always your own devices — a Mac, phone, or another Linux box on your home network with a mistyped username or old SSH key. Verify the IP with arp -a before treating it as a threat.
Inspect Open Network Connections
A compromised server often maintains persistent outbound connections to attacker-controlled infrastructure — command-and-control servers, data exfiltration endpoints, or reverse shells. Inspecting active network connections takes seconds and immediately reveals anything unexpected.
Check All Listening and Active Connections
sudo netstat -tulnp
# Show all active connections including established outbound
sudo netstat -tanp
# Alternative using ss (more modern)
sudo ss -tulnp
# Show only established outbound connections
sudo ss -tnp state established
Expected Open Ports on a Clean System
| Port | Process | Expected? |
|---|---|---|
| 22 (TCP) | sshd | ✓ Yes — SSH access |
| 8787 (TCP) | rootlessport (Podman) | ✓ Yes — WordPress |
| 127.0.0.1:20241 (TCP) | cloudflared | ✓ Yes — Tunnel |
| UDP high ports | cloudflared | ✓ Yes — QUIC/tunnel |
| 4444, 1337, 8888 | unknown | ✗ Investigate immediately |
| Any port, unknown process | ??? | ✗ Investigate immediately |
Outbound connections on ports 4444, 1337, 8888, or 31337 are classic reverse shell indicators. Any listening service you do not recognize — especially one owned by www-data or an unknown user — should be treated as a backdoor until proven otherwise. Identify the owning process with sudo lsof -i :[port] immediately.
Run Free Online Security Scans
Server-side checks tell you what is happening inside your system. External scans tell you what the world sees — malware injected into your HTML, blacklist status, and whether search engines have flagged your site as dangerous. Run these after completing your server-side audit.
The gold standard free WordPress malware scanner. Checks for injected malware, blacklist status across major databases, outdated software, and security anomalies visible in your page source.
Free
Check whether Google has flagged your site as dangerous. If your site appears in Google Safe Browsing it will show warnings to Chrome users and destroy your search rankings.
Free
Scan your URL against 70+ security vendors simultaneously. Provides the most comprehensive external reputation check available for free. A clean result here is strong evidence your site is not compromised.
Free
Run All Three in Under 2 Minutes
https://sitecheck.sucuri.net
https://transparencyreport.google.com/safe-browsing/search
https://www.virustotal.com/gui/home/url
# All three should return clean results within 60 seconds each
Prevent Dangerous Auto-Upgrades on Alpine Linux
Alpine Linux does not auto-upgrade by default — but a single manual apk upgrade can silently bump critical packages like Podman across major versions, breaking your entire container stack. The safest approach is to always preview upgrades before applying them, and to use the –ignore flag to skip packages that could break your stack.
Always Preview Before Upgrading
sudo apk upgrade –simulate
# Step 2: Review the output carefully for critical packages
# Look for: podman, crun, runc, slirp4netns, containers-common
# Any of these upgrading could affect your container stack
# Step 3: Upgrade everything EXCEPT podman
sudo apk upgrade –ignore podman
# Step 4: Upgrade podman only when you have time to troubleshoot
sudo apk add podman
Podman v5 changed its default rootless networking (slirp4netns to pasta), default OCI runtime (runc to crun), and cgroup handling — all in a single major version bump. Without the right configuration in place, upgrading Podman will leave your containers unable to start after the next reboot. Always test Podman upgrades during a maintenance window, never during a routine system update.
Pin Critical Packages on Alpine Linux
Package pinning locks a specific package to its current version, preventing it from being upgraded during routine apk upgrade runs. On a production server hosting a live site, pinning Podman and its dependencies is a simple one-command protection against unplanned breakage.
Pin Podman to Current Version
apk info podman
# Pin to current version (replace 5.5.1-r0 with your actual version)
sudo apk add ‘podman=5.5.1-r0’
# Verify the pin is in place
apk info podman | head -1
# To unpin later when ready to upgrade intentionally
sudo apk add podman
Safe Update Workflow for Production Servers
-
Simulate First
Always run sudo apk upgrade –simulate before any real upgrade. Read the full output and identify any container-related packages in the list.
-
Upgrade Safely Excluding Critical Packages
Run sudo apk upgrade –ignore podman to get all security patches and library updates without touching your container runtime.
-
Test Podman Upgrades in a Maintenance Window
When you are ready to upgrade Podman, do it when you have 30–60 minutes to troubleshoot. Have your containers.conf configuration backed up. Know the fallback steps.
-
Verify After Every Reboot
After any system reboot run podman-compose ps immediately. Both containers should show status Up. If they show Created or Exited your container runtime configuration needs attention before the site will serve traffic.
Full Hack Detection Audit Checklist
Run through this checklist any time your site goes down unexpectedly, you notice unusual behavior, or you simply want confidence that your server is clean. The full checklist takes under 10 minutes on the command line.
Run grep -r “eval(base64_decode” on wp_data directory — no output means clean
Run grep -r “eval(gzinflate” on wp_data directory — no output means clean
Check for PHP files modified in the last 7 days — review any unexpected results
Audit wp_users table — confirm you recognize every user and their role
Review failed SSH logins — identify all source IPs in /var/log/messages
Run netstat -tulnp — confirm all listening ports match expected services
Run Sucuri SiteCheck on your domain — confirm clean result
Run VirusTotal URL scan — confirm zero detections across vendors
Check Google Safe Browsing status — confirm no warnings
Confirm podman is pinned — apk info podman shows correct locked version
Confirm containers.conf is correct — cgroups=disabled, runtime=crun, slirp4netns
Run apk upgrade –simulate before any future upgrades — review output before applying