How to Secure a Coolify VPS Without Breaking Coolify
However, a Coolify server can look locked down in UFW while Docker-published ports are still reachable from the public internet. This guide builds a safer setup in four layers: network policy, SSH, account security, and automatic security updates—without cutting Coolify off from the server it manages.
One public edge. Fewer surprises.
Keep apps behind Coolify’s reverse proxy and stop exposing management ports directly.
TL;DR
UFW protects host traffic, but Docker can publish container ports through forwarding rules that do not behave the way many people expect from a normal UFW deny rule. Put the Coolify dashboard on a proper HTTPS domain, allow only the host ports you actually need, then enforce a default-deny policy for Docker-forwarded traffic in DOCKER-USER. After that, harden SSH, enable dashboard 2FA, narrow API token permissions, and automate security upgrades.
⚡ The fast path
- Probe the server from a different machine and record which ports answer.
- Move the Coolify dashboard to a real domain with HTTPS before closing management ports.
- Configure UFW for the host: SSH, HTTP and HTTPS only.
- Add a persistent
DOCKER-USERpolicy that drops unexpected traffic heading to published containers. - Verify again from outside, then harden SSH, Coolify authentication and updates.
Security terms you should know first
eth0, ens3 or something similar.What the finished setup should look like
The four layers of a safer Coolify server
Control Docker traffic
Use UFW for host traffic and a DOCKER-USER policy for published containers.
Layer 2 · SSHHarden the management channel
Keep Coolify’s machine access working while removing password logins and limiting brute force.
Layer 3 · AccountProtect the control panel
Close registration, enable 2FA and issue narrowly scoped API tokens.
Layer 4 · UpdatesKeep the box patched
Enable security updates without scheduling surprise reboots for your production apps.
Open a second SSH session and keep it connected. If your VPS provider offers a browser console or rescue console, know where it is before you start. A typo in SSH or firewall configuration can lock you out.
The firewall Docker can route around
For example, on a traditional server, you might assume that ufw deny 8000 means port 8000 is unreachable. Docker changes the packet path when a container port is published. Traffic can be translated and forwarded toward the container before it follows the same path as traffic addressed directly to the host.
Therefore, the practical lesson is not “UFW is useless.” UFW still protects services terminating on the host. The lesson is that container forwarding needs its own policy. Docker intentionally gives administrators a place for that policy: the DOCKER-USER chain.
First: check what the internet can reach
First, run the test from your laptop or another machine on the internet, not from the VPS itself. Testing locally can bypass the path you are trying to measure.
Linux / WSL / Git Bash
for p in 22 80 443 6001 6002 8000; do
timeout 5 bash -c "exec 3<>/dev/tcp/SERVER_IP/$p" 2>/dev/null \
&& echo "$p OPEN" || echo "$p closed"
done
macOS
for p in 22 80 443 6001 6002 8000; do
nc -z -w 5 SERVER_IP $p 2>/dev/null \
&& echo "$p OPEN" || echo "$p closed"
done
Windows PowerShell
foreach ($p in 22,80,443,6001,6002,8000) {
if (Test-NetConnection SERVER_IP -Port $p -InformationLevel Quiet -WarningAction SilentlyContinue) {
"$p OPEN"
} else {
"$p closed"
}
}
Because not every port speaks HTTP. A raw TCP connection test asks the simpler question: did something accept a connection on this port?
Why Docker-published ports need a different firewall layer
Traffic addressed to the host
Traffic heading to a published container
As a result, that second lane is why a firewall policy for Docker belongs in DOCKER-USER. It gives you a predictable place to accept approved public traffic and drop everything else before it reaches published containers.
Common Coolify-related ports
| Port | Typical purpose | Publicly needed? |
|---|---|---|
22/tcp | SSH | Usually yes, unless you use a private tunnel/VPN. |
80/tcp | HTTP / certificate redirects / proxy | Usually yes. |
443/tcp | HTTPS / application traffic | Yes. |
443/udp | HTTP/3 / QUIC where enabled | Optional, but commonly allowed with modern web proxies. |
6001 | Coolify realtime services on some setups | No direct public exposure once routed through the dashboard domain. |
6002 | Terminal/realtime path on some setups | No direct public exposure once routed through HTTPS. |
8000 | Direct Coolify dashboard access | Prefer the HTTPS domain instead. |
Put the Coolify dashboard on a domain before closing ports
First, do this before the restrictive Docker policy. Create a DNS A record such as coolify.example.com pointing to your VPS. In Coolify, set the instance domain to the full HTTPS URL and wait for the proxy and certificate to become healthy.
https://coolify.example.com
Next, load the dashboard using the new domain. Confirm the browser sees a valid TLS certificate, live logs work, and the terminal can connect. Only after that should you remove direct public access to the old management ports.
The domain becomes the supported public entry point on 443. That lets you close direct dashboard and realtime ports without sacrificing the dashboard features that are proxied through HTTPS.
Part 1: configure UFW for host traffic
Next, run these commands as root. Allow SSH before enabling the firewall.
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable
ufw status verbose
Coolify may make repeated SSH connections as part of normal server management. A generic connection-rate rule can interfere with that traffic. Key-only authentication plus fail2ban for failed logins is usually a cleaner control.
Part 2: add a persistent policy for Docker-forwarded traffic
First, identify the public network interface:
ip -4 route show default
For example, the result will usually include an interface such as eth0, ens3 or enp1s0. The example below uses eth0. Replace it if yours is different.
Before continuing, back up UFW’s existing after-rules file:
cp /etc/ufw/after.rules /etc/ufw/after.rules.bak
Afterward, append a separate filter table to the end of /etc/ufw/after.rules, after the existing sections have completed:
*filter
:DOCKER-USER - [0:0]
-A DOCKER-USER -m conntrack --ctstate RELATED,ESTABLISHED -j RETURN
-A DOCKER-USER ! -i eth0 -j RETURN
-A DOCKER-USER -i eth0 -p tcp -m multiport --dports 80,443 -j RETURN
-A DOCKER-USER -i eth0 -p udp --dport 443 -j RETURN
-A DOCKER-USER -i eth0 -j DROP
COMMIT
This policy does four important things: it keeps established connections working, leaves non-public interfaces alone, allows normal public web traffic, and drops other new traffic arriving on the public interface before it reaches a Docker-published service.
Reload UFW and inspect the active chain:
ufw reload
iptables -L DOCKER-USER -n -v --line-numbers
If you manage the Docker-user policy through UFW’s rule files, avoid layering an unrelated persistence package on top unless you understand how the two systems interact. Keep the configuration auditable.
Verify the result from outside
Run the same port probe again from your laptop. A typical public result after the policy is in place should resemble this:
22 OPEN SSH
80 OPEN HTTP
443 OPEN HTTPS
6001 closed realtime
6002 closed terminal/realtime
8000 closed direct dashboard
Then test the things that matter inside Coolify: dashboard login, live logs, browser terminal, a deployment, and HTTPS access to a real application.
Why default-deny is stronger than blocking a short list of ports
If you only write rules for ports 6001, 6002 and 8000, a database you publish later can become reachable without your firewall policy noticing. A default-deny policy is future-facing: new published services remain blocked until you deliberately allow them.
Add a narrow exception before the final DROP rule—ideally restricted to a trusted source IP or private VPN rather than the entire internet. Public databases attract scanning within minutes.
Harden SSH without disconnecting Coolify
Coolify relies on SSH for server management. That means generic advice such as “disable root completely” can be wrong for a server where Coolify itself expects root access. The safer goal is to make root key-only and remove password authentication.
1. Confirm you can log in with an SSH key
Before changing anything, open a second terminal and verify a new SSH connection succeeds using your private key. Do not continue if your only working login method is a password.
2. Create an SSH drop-in instead of rewriting the whole file
PermitRootLogin prohibit-password
PasswordAuthentication no
PubkeyAuthentication yes
Validate the configuration before restarting SSH:
sshd -t
If that prints nothing, syntax validation passed. Then reload or restart the SSH service:
systemctl restart ssh
Immediately test a fresh SSH login in a new terminal. Keep the old session open until you know both your login and Coolify’s server connection still work.
3. Add fail2ban for repeated failed logins
apt update
apt install -y fail2ban
[sshd]
enabled = true
backend = systemd
maxretry = 5
findtime = 10m
bantime = 1h
systemctl enable --now fail2ban
fail2ban-client status sshd
For day-to-day administration, consider a separate sudo-capable user with its own key. If you add SSH 2FA for humans, scope it carefully so the machine account Coolify uses is not forced to provide an interactive code.
4. Consider private SSH instead of public SSH
The strongest reduction in attack surface is to remove public SSH altogether and reach the server through a private network such as Tailscale or a properly configured Cloudflare Tunnel. This changes the architecture, so validate Coolify’s connectivity requirements before closing port 22.
Layer 3 of 4 · AccountTreat the Coolify dashboard like a root credential
A Coolify admin account can deploy code, read environment variables, open terminals and manage databases. Protect it as carefully as you protect SSH.
Close registration after the first admin account is created
On a newly installed instance, finish the initial account setup promptly. Then check Coolify’s authentication settings and make sure public account creation is not left open unless you intentionally operate a multi-user environment.
Enable two-factor authentication
Open your user security settings in Coolify and enable TOTP-based two-factor authentication. Store recovery codes somewhere separate from the VPS and separate from the password manager entry if possible.
That separation is useful. You can strengthen the human control panel login without introducing an interactive step into Coolify’s own machine-to-machine SSH workflow.
Scope API tokens to the smallest permission set
API tokens should be task-specific. A deployment automation rarely needs the same power as a root administrator. If a token only needs to trigger deployments, do not also grant access to sensitive secrets or broad administrative operations.
| Token type | Safer use | Risk if leaked |
|---|---|---|
| Read-only | Inventory and monitoring | Lower, but still reveals infrastructure metadata. |
| Deploy-focused | CI/CD deployment trigger | Can change what is running; keep repository controls strong. |
| Write / admin | Infrastructure automation that truly needs changes | High. |
| Root / sensitive | Only where unavoidable | Very high; treat like an admin password. |
Rotate tokens that have been exposed in logs, screenshots, shell history or public repositories. Never embed privileged tokens directly in frontend JavaScript.
Layer 4 of 4 · UpdatesInstall security patches automatically
A server that is secure today can become vulnerable when a new kernel, SSH, libc or OpenSSL issue is disclosed. Automatic security updates reduce the gap between a patch becoming available and you installing it.
Enable unattended upgrades
apt update
apt install -y unattended-upgrades
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
You can inspect the timers and logs to verify the mechanism is active:
systemctl status apt-daily.timer
systemctl status apt-daily-upgrade.timer
journalctl -u unattended-upgrades --no-pager -n 50
Package updates can install automatically while you still control when the whole VPS reboots. Schedule kernel-required reboots during a maintenance window and verify applications return healthy afterwards.
Apply all four layers in this order
- Back up and verify console access. Make sure you can recover from a bad firewall or SSH rule.
- Put Coolify on an HTTPS domain. Verify dashboard, logs and terminal.
- Probe ports from outside. Save the before-state.
- Enable the host UFW policy. Allow SSH, HTTP and HTTPS.
- Add the DOCKER-USER default-deny policy. Reload and inspect counters.
- Probe again. Confirm direct management ports are closed.
- Harden SSH. Key-only root, no password authentication, fail2ban.
- Protect Coolify itself. Close registration, enable 2FA, reduce API-token scope.
- Enable unattended security updates. Keep automatic rebooting under your control.
- Deploy a test app. Confirm HTTPS, logs, terminal and future deployments still work.
Final external port check
Your exact architecture may differ, but for a common public Coolify host the goal is a deliberately small surface:
22/tcp OPEN SSH (or close if using a private tunnel)
80/tcp OPEN HTTP / ACME / redirects
443/tcp OPEN HTTPS
443/udp OPEN optional HTTP/3
6001 closed no direct public access
6002 closed no direct public access
8000 closed no direct public access
5432 closed database unless explicitly required
6379 closed Redis unless explicitly required
What this guide does not replace
In addition, firewall and account hardening are only part of server security. A production self-hosted environment should also include tested backups, off-site copies, application-level authentication, secret rotation, monitoring, least-privilege database accounts, DNS security, and a recovery plan. If the data matters, test restoration—not just backup creation.
Recommended official resources
Coolify setup and SSH documentation
First, use the platform documentation when you confirm current Coolify behavior. These references are especially useful before changing SSH access or dashboard networking.
Use the official documentation for current installation, server, security and integration behavior.
Helpful when changing SSH authentication or adding a remote server to Coolify.
Private access and Docker firewall references
In addition, review the networking references below when you want to reduce public SSH exposure or understand how Docker handles forwarded traffic.
An option for reducing direct public SSH exposure in architectures that support it.
Background on Docker’s firewall behavior and administrator filtering chains.
Frequently asked questions
Firewall and Docker questions
Why can a Docker-published port still answer after I add a UFW deny rule?
Because Docker-published traffic can follow a forwarding path created by Docker’s NAT and filter rules rather than behaving like a service listening directly on the host. Put your policy in the Docker-aware forwarding path—commonly the DOCKER-USER chain—and verify from another machine.
Does that mean UFW is useless on a Coolify VPS?
However, UFW still provides valuable protection for host services such as SSH. The key is to understand that forwarded container traffic needs an additional policy.
Can I close ports 6001, 6002 and 8000?
First, do it only after your Coolify instance domain is working over HTTPS and you have verified the dashboard, logs and browser terminal through the domain. Closing management ports too early can remove the path you are currently using.
Will a DOCKER-USER default-deny policy block a database I publish later?
Yes. As a result, if the database is published through Docker and the traffic arrives through the public interface, the final DROP rule should block it until you add a narrower allow rule above it. That is a security advantage.
SSH, account, and service questions
Should I disable root SSH completely?
However, not automatically on a Coolify-managed server. If Coolify depends on root SSH, fully disabling it can break management. Key-only root login is a practical compromise when required by the platform. Test your exact setup before changing this behavior.
Is 2FA on Coolify enough?
No. In addition, it protects interactive dashboard login, but you still need SSH hardening, careful API-token permissions, firewall controls, updates and backups.
Should I expose PostgreSQL or Redis publicly?
Prefer not to. For example, if a remote client truly requires access, use a private network or restrict the firewall rule to known source IPs. Never expose a database globally just for convenience.
How do I know the firewall actually works?
Finally, test from outside the server. Internal tests can bypass the same packet path used by internet traffic. Re-run your probe after every significant firewall change and after a reboot.
Build the server so every public port is intentional
Ultimately, the goal is not to collect the largest number of hardening commands. It is to understand which traffic path each rule protects, minimize the public attack surface, and verify the result from outside the VPS.
Read Coolify Docs Review Network LayerEditorial note: This article is an original technical guide. Commands that change SSH or firewall behavior can lock you out if applied incorrectly. Keep a second SSH session and your VPS provider’s console available, and adapt interface names, ports and policies to your actual server.