Updated and tested 23 October 2026
Quick answer first, because most people arrive mid-task: the Portainer default port is 9443, HTTPS, for the web UI. Port 9000 is the old HTTP interface and modern installs don't publish it. Port 8000 belongs to Edge agents and stays closed unless you use them. That's the whole map; the rest of this page is what each one means in practice, when 9000 still earns a place and which ports face the internet on a real server.
What each Portainer port does
A standard install publishes two ports and people remember a third from older tutorials:
- 9443/TCP, the UI over HTTPS with a self-signed certificate out of the box. Every current guide, including our Portainer install walkthrough, points you here.
- 8000/TCP, a tunnel server for Edge agents (Portainer's mechanism for managing remote machines behind NAT). No Edge agents, no reason for this port to exist publicly; it rides along in the official run command and can be dropped from it entirely.
- 9000/TCP, the legacy HTTP UI. Portainer served plain HTTP here for years, which is why the internet's muscle memory says 9000. It still works if you publish it (
-p 9000:9000), and unencrypted admin panels stopped being acceptable a while ago, so treat it as a compatibility escape hatch rather than an option.
The one case where I still publish 9000 on purpose: behind a reverse proxy on the same machine, where the proxy terminates real HTTPS and talks plain HTTP to Portainer over localhost. Proxying to 9443's self-signed HTTPS works too but needs the proxy told to skip upstream verification, and plain-HTTP-on-localhost is the simpler contract. Either way the proxy owns the public side; the pattern is the same one our Nginx reverse proxy guide builds.
Firewall rules for each kind of setup
Three scenarios cover nearly everyone. On a home LAN box, allow 9443 from your local subnet and open nothing in the router; Portainer has no business being internet-reachable from a household setup. On a VPS you admin alone, my preferred arrangement is 9443 closed in the cloud firewall and reached through an SSH tunnel (ssh -L 9443:localhost:9443 user@server), which costs one command per session and removes the login page from the internet entirely; nothing else on the machine is worth more to an attacker than a panel that launches privileged containers. On a VPS a small team shares, the tunnel gets old, so front it with the reverse proxy on 443, restrict by IP where the team's addresses allow it and keep 9443 and 8000 off the public interface. The panel firewall on our Portainer VPS plans handles the cloud-side half of those rules.
Ubuntu-side, one trap first: Docker writes its own iptables rules for every port it publishes, and those rules sit outside ufw's reach. With sudo ufw deny 9443/tcp active on my test server, the login page still loaded from my other machine.
My other machine got a 200 from port 9443 while the ufw deny rule was in place.
For the tunnel-only case, publish 9443 on localhost in the run command or compose file, so only the SSH tunnel above can reach it. I haven't tested this binding on my server, so check from another machine afterwards that 9443 no longer answers:
-p 127.0.0.1:9443:9443
For the LAN and team cases, filter in Docker's DOCKER-USER chain instead. The form below is the one in Docker's Docker with iptables page: the first rule drops anything arriving on the public interface (replace eth0) that doesn't come from your subnet, and the second, which lands above it because -I inserts at the top, lets replies to the containers' own outgoing connections through. Run them in this order. On my test server I filtered two single addresses with separate allow and drop rules, so this subnet version is the docs' rule, not one I ran. Either way the rules apply to every port Docker publishes on that interface, Portainer's included, and they're gone after a reboot unless you save them.
sudo iptables -I DOCKER-USER -i eth0 ! -s 192.168.1.0/24 -j DROP
sudo iptables -I DOCKER-USER -m state --state RELATED,ESTABLISHED -j ACCEPT



