You are currently viewing How to Secure a Coolify VPS: Complete Security Guide (2026)
Secure Coolify VPS

How to Secure a Coolify VPS: Complete Security Guide (2026)

Self-hosting security guide

How to Secure a Coolify VPS Without Breaking Coolify

Updated for Coolify v4•Ubuntu 24.04•Practical firewall + SSH hardening

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.

22 / SSHOPEN
80 / HTTPOPEN
443 / HTTPSOPEN
6001 / realtimeBLOCKED
6002 / terminalBLOCKED
8000 / dashboardBLOCKED
🛡️ PUBLIC SURFACE: 22 · 80 · 443

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

  1. Probe the server from a different machine and record which ports answer.
  2. Move the Coolify dashboard to a real domain with HTTPS before closing management ports.
  3. Configure UFW for the host: SSH, HTTP and HTTPS only.
  4. Add a persistent DOCKER-USER policy that drops unexpected traffic heading to published containers.
  5. Verify again from outside, then harden SSH, Coolify authentication and updates.

Security terms you should know first

PortA numbered network entry point. SSH commonly uses 22 and HTTPS uses 443.
Published portA host port Docker maps to a container so external traffic can reach the service.
UFWUbuntu’s uncomplicated firewall, typically used to manage host firewall rules.
DOCKER-USERA Docker-provided firewall chain intended for administrator-defined filtering before traffic reaches containers.
Reverse proxyA front door that receives web traffic and routes it to the correct app by hostname. Coolify commonly uses Traefik.
Network interfaceThe server interface facing the network, commonly named eth0, ens3 or something similar.

What the finished setup should look like

Public web traffic enters through ports 80 and 443.
SSH remains available, preferably with key-only authentication.
Direct Coolify management ports are not exposed publicly.
Published databases are blocked unless explicitly allowed.
The Coolify account uses two-factor authentication.
Security package updates install automatically.

The four layers of a safer Coolify server

⚠ Before you change firewall or SSH settings

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

Run on your laptopreplace SERVER_IP
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

Run in Terminalreplace SERVER_IP
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

Run in PowerShellreplace SERVER_IP
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"
  }
}
Why not just use curl?

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

Internet
→
INPUT
→
UFW rules
→
sshd

Traffic heading to a published container

Internet
→
DNAT / FORWARD
→
DOCKER-USER
→
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

PortTypical purposePublicly needed?
22/tcpSSHUsually yes, unless you use a private tunnel/VPN.
80/tcpHTTP / certificate redirects / proxyUsually yes.
443/tcpHTTPS / application trafficYes.
443/udpHTTP/3 / QUIC where enabledOptional, but commonly allowed with modern web proxies.
6001Coolify realtime services on some setupsNo direct public exposure once routed through the dashboard domain.
6002Terminal/realtime path on some setupsNo direct public exposure once routed through HTTPS.
8000Direct Coolify dashboard accessPrefer 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.

Example instance domainCoolify → Settings
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.

✓ Why this ordering matters

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.

Server shellroot
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
Do not blindly rate-limit port 22 on a Coolify box.

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:

Server shellfind public 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:

/etc/ufw/after.rulesreplace eth0 if necessary
*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:

Server shellapply + inspect
ufw reload
iptables -L DOCKER-USER -n -v --line-numbers
Use one persistence mechanism, not several.

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:

Expected public surfaceexample
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.

Need a database to be public?

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

/etc/ssh/sshd_config.d/99-coolify-hardening.confserver
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
/etc/fail2ban/jail.localbasic SSH jail
[sshd]
enabled = true
backend = systemd
maxretry = 5
findtime = 10m
bantime = 1h
systemctl enable --now fail2ban
fail2ban-client status sshd
Optional: human SSH accounts.

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.

Treat 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.

2FA protects the dashboard login—not SSH.

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 typeSafer useRisk if leaked
Read-onlyInventory and monitoringLower, but still reveals infrastructure metadata.
Deploy-focusedCI/CD deployment triggerCan change what is running; keep repository controls strong.
Write / adminInfrastructure automation that truly needs changesHigh.
Root / sensitiveOnly where unavoidableVery 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.

Install 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
/etc/apt/apt.conf.d/20auto-upgradesdaily security automation
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
Avoid surprise automatic reboots on a production Coolify server.

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

  1. Back up and verify console access. Make sure you can recover from a bad firewall or SSH rule.
  2. Put Coolify on an HTTPS domain. Verify dashboard, logs and terminal.
  3. Probe ports from outside. Save the before-state.
  4. Enable the host UFW policy. Allow SSH, HTTP and HTTPS.
  5. Add the DOCKER-USER default-deny policy. Reload and inspect counters.
  6. Probe again. Confirm direct management ports are closed.
  7. Harden SSH. Key-only root, no password authentication, fail2ban.
  8. Protect Coolify itself. Close registration, enable 2FA, reduce API-token scope.
  9. Enable unattended security updates. Keep automatic rebooting under your control.
  10. 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:

Public internet viewtypical target
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.

📘
Coolify Documentation

Use the official documentation for current installation, server, security and integration behavior.

🔐
Coolify OpenSSH Guide

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.

🌐
Coolify + Cloudflare Tunnel SSH

An option for reducing direct public SSH exposure in architectures that support it.

🐳
Docker packet filtering and firewalls

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 Layer

Editorial 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.

Vanel Sylvestre

I am Vanel Sylvestre , welcome to my world, i am a real estate investor, business owner and also i am an affiliate marketer with over 10 years of experience in online marketing i have been making thousands Online Using Online Marketing Tools. In This blog We share some online marketing tools that can help you grow your business, if this is something you are interested in, one more time welcome to my world.

Leave a Reply