// DevOps
A new server isn't a blank slate. It's an unlocked door.
Published on 2026-05-28
A few days ago I investigated a compromise of a production server. Not theoretical — a real one, with a Monero miner, a C2 agent and a backdoor. The attack was fully automated. No one was specifically hunting this server — a bot continuously scans the entire Internet, tries default passwords on open databases and does its job.
From the moment the server appeared on the network to the first attack took less than six hours. To a successful breach — less than a day.
Three conditions that made it possible: PostgreSQL was exposed, the password was postgres, the firewall was not enabled. That’s it. Nothing else was required.
Why this happens even with experienced people
There is a certain psychology with a new server. You just brought it up, everything works, the app responds — and you move on to the next task. Security seems like something you can set up later. Later — when there is time. Later — before production. Later — after release.
Later never comes. Or it comes, but already in the form of top showing /tmp/mysql at 400% CPU.
A new server on the Internet is not a blank slate. It’s an unlocked door in a house located on a busy street. Scanners find it within hours, not days.
What actually happened
The attacker found an open port 5432 on the Internet. They ran a brute-force. The password postgres — this is not even brute-force, it’s the first line in any dictionary. They gained access to PostgreSQL with superuser privileges.
Next comes a legitimate PostgreSQL feature — COPY FROM PROGRAM. It allows a superuser to execute an arbitrary shell command directly from an SQL query. The attacker passed a base64-encoded bash script through it, which downloaded and ran a dropper.
But the cleverest part was the persistence mechanic. Event triggers were added to the database — triggers that fire on every DDL operation (CREATE TABLE, ALTER, DROP…). On each such event they silently recreated a superuser role with a password known to the attacker. So even if an admin noticed and removed the malicious role — the next migration or CREATE INDEX would automatically restore it.
Elegant and truly nasty.
Three rules that stop 99% of such attacks
I’m intentionally not writing “ten rules” or a “complete checklist”. Not because the rest isn’t important — but because these three rules close off the majority of automated attacks that actually occur in the wild.
Databases must not be accessible from the Internet. PostgreSQL, MySQL, Redis, MongoDB — none of them are intended for direct external access. In docker-compose.yml the line "5432:5432" for a database is almost always a mistake. The application will reach the database inside the Docker network by hostname. Externally this port is only useful to attackers.
If you need access for development — bind only to localhost:
ports:
- "127.0.0.1:5432:5432"
For production — remove ports entirely.
Default passwords are not passwords. postgres, password, root, admin — these are the top entries in any brute-force dictionary. A scanner will try them in seconds, not hours. Generate a password at deploy time:
POSTGRES_PASSWORD=$(openssl rand -base64 32)
Or use secret management: HashiCorp Vault, AWS Secrets Manager, Doppler. The main thing — never commit passwords to the repository and don’t leave default values in .env.
The firewall must be enabled from the first minute. Not before release. Not after setup. From the first minute after creating the server. The right model — deny everything, allow only what’s necessary:
ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
ufw enable
After that open specific ports as needed. Not the other way around.
About monitoring you should add
The three rules above are about prevention. But if you also want to see what’s happening, add minimal logging to PostgreSQL. By default it doesn’t log successful connections or DDL statements. That means you have no visibility into what’s happening with the database.
In postgresql.conf:
log_connections = on
log_disconnections = on
log_statement = 'ddl'
And a simple cron script to check for anomalies:
#!/bin/bash
SUPERUSERS=$(psql -U postgres -t -c "SELECT count(*) FROM pg_user WHERE usesuper = true AND usename != 'postgres';")
if [ "$SUPERUSERS" -gt 0 ]; then
echo "ALERT: unexpected superusers detected" | mail -s "Security Alert" admin@example.com
fi
This is already enough to notice something suspicious earlier than top will.
Why this matters more than it seems
According to Shodan, over 800,000 PostgreSQL instances are exposed on the Internet. A significant portion use default or weak passwords. No one is hacking them manually — these are fully automated scanners running around the clock. They don’t choose victims by business size or value of data. They just go down the list of open ports.
Your server is no exception. It’s already on that list. The question is only whether there is a lock on the door.
Protecting against this doesn’t require complex technologies, expensive tools, or deep security expertise. It requires three things that can be done in ten minutes on the first login to the server. Just don’t postpone them.
// Reviews
Related reviews
I came with an expensive request to configure a VPS server, but during the consultation Mikhail suggested a much simpler, more affordable solution. In the end I saved time and money. Mikhail — a true expert who works for the client's result, not for the fee. I recommend him!
I came with an expensive request to configure a VPS server, but during the consultation Mikhail suggested a much simpler and more cost-effective solution. In the end I saved budget and time. Mikhail — a true expert who …
VPS setup, server setup
2026-05-12 · ★ 5/5
Excellent work! Set up the server very quickly, installed the control panel, and configured the IP. Definitely recommend!
Excellent work! Very quickly set up the server, installed the panel, configured the IP I can definitely recommend it!
Everything was excellent; helped promptly and professionally. Thank you — I recommend them to the community.
Everything's great, helped promptly and professionally, thank you, I recommend it to the community
VPS setup, server setup
2026-04-16 · ★ 5/5
There were several issues concerning both the technical side and overall understanding. Mikhail responded quickly, resolved the technical problems, and helped me understand them — many thanks. I'm satisfied with the result.
There were several issues concerning both the technical side and overall understanding. Mikhail responded quickly to the request, helped sort things out and resolved the technical problems and helped clarify …
VPS setup, server setup
2026-02-18 · ★ 5/5
Everything was done quickly and efficiently. I recommend.
Everything was done quickly and efficiently. I recommend.
VPS setup, server setup
2026-01-17 · ★ 5/5
Everything went well; the contractor responded quickly to questions and helped resolve the issue. Thanks!
Everything went well, the contractor responded quickly to questions and helped resolve the issue. Thank you!
VPS setup, server setup
2025-12-16 · ★ 5/5
// Contact
Need help?
Get in touch with me and I'll help solve the problem
// Related