Arr stack ports: what each app listens on
Default ports for Sonarr, Radarr, Prowlarr, Bazarr, qBittorrent, Jellyfin, Seerr and FlareSolverr, plus how to keep every one of them off the public internet.
The ports, and the rule that matters more
Arr stack ports are simple to list: Sonarr listens on 8989, Radarr on 7878, Prowlarr on 9696, qBittorrent's web UI on 8080, Jellyfin on 8096 and Seerr on 5055. One rule matters more than any number: on a VPS, none of these ports should be published to the public internet. Bind each one to 127.0.0.1 and put one reverse proxy in front of them. That proxy owns TLS (transport layer security) for every app.
The list below was checked against each project's own documentation on 19 September 2026, because defaults have moved between major versions and copied compose files keep old values alive. The list cannot answer four things, and they follow it: which of these ports to publish (none, directly), how to bind them so a firewall mistake cannot expose them, what network_mode: service:gluetun does to qBittorrent's ports, and why ufw never sees a port Docker has published. The full stack, with volumes and environment, is in the arr stack Docker Compose guide.
Arr stack ports: the defaults, checked September 2026
Every port is TCP unless marked. The number is what the app listens on inside its container, which is also the number the official images publish by default.
- Sonarr: 8989, web UI and API.
- Radarr: 7878, web UI and API.
- Lidarr: 8686, web UI and API.
- Readarr: 8787, web UI and API. The project was retired and its repository archived on 27 June 2025, and the LinuxServer image is marked deprecated, so treat this entry as historical.
- Prowlarr: 9696, web UI and API.
- Bazarr: 6767, web UI and API.
- qBittorrent: 8080 for the web UI (
WEBUI_PORT), plus 6881 TCP and 6881 UDP for peer connections (TORRENTING_PORT). - Jellyfin: 8096 for HTTP, 8920 for HTTPS with your own certificate (optional), 7359 UDP for client discovery on the local network (optional), 1900 UDP for DLNA discovery (optional).
- Seerr: 5055 (
PORT). Jellyseerr and Overseerr have merged into this one project, and the image isghcr.io/seerr-team/seerr. The old Jellyseerr and Overseerr images used the same port. - FlareSolverr: 8191 (
PORT).
Three notes on that list. Jellyfin's two UDP ports exist so that TVs and phones on the same LAN can find the server without typing an address, which never happens on a VPS, so leave them out. qBittorrent's 6881 is a peer port, not a web port, and the gluetun section below explains why it should not be published either. FlareSolverr only ever talks to Prowlarr, and the FlareSolverr README itself publishes it as 127.0.0.1:8191:8191.
Which arr stack ports should a VPS publish? None of them, directly
The apps do not need published ports to talk to each other. Containers in one compose project share a network, and Sonarr reaches Prowlarr at http://prowlarr:9696 by service name whether or not 9696 is published on the host. A published port exists for one reason only: so something outside the compose network can reach the app. On a VPS the only thing that should be reaching a web UI is the reverse proxy on the same machine.
There is a good reason to be strict. Prowlarr holds the credentials for every indexer you use. Each arr app exposes an API that a single API key unlocks, and that key is shown in plain text under Settings. Sonarr and Radarr have no default username or password at all; you create one on first run, and if that login is ever lost the way back in is an edit to config.xml on the host. qBittorrent's web UI starts with a temporary password that is printed to the container log, and if you never change it a new one is generated on every restart. None of that belongs on a public IP where anything can try it.
Bind every port to 127.0.0.1
A ports: line without an address binds to 0.0.0.0, which is every interface, including the public one. Put the loopback address in front of the host port and the port only answers on the VPS itself.
services:
sonarr:
image: lscr.io/linuxserver/sonarr:latest
ports:
- "127.0.0.1:8989:8989"
radarr:
image: lscr.io/linuxserver/radarr:latest
ports:
- "127.0.0.1:7878:7878"
prowlarr:
image: lscr.io/linuxserver/prowlarr:latest
ports:
- "127.0.0.1:9696:9696"
flaresolverr:
image: ghcr.io/flaresolverr/flaresolverr:latest
# no ports: only Prowlarr talks to it, over the compose networkVolumes, PUID, PGID and the folder layout that makes hardlinks work are left out here on purpose; they are covered in the arr stack folder structure and hardlinks guide. Bring the project up and check what is actually listening.
docker compose up -d
sudo ss -tlnp | grep -E ':(8989|7878|9696)'Every line should show 127.0.0.1:8989, 127.0.0.1:7878 and so on in the local address column. A line that reads 0.0.0.0:8989 or *:8989 is a port published to the world, because a ports: entry somewhere is missing its 127.0.0.1: prefix. docker compose ps shows the same thing in its PORTS column as 127.0.0.1:8989->8989/tcp. From another machine, curl -m 5 http://203.0.113.10:8989 should end in Connection refused or Connection timed out, and both mean the port is closed. If you want to test more than a few ports, checking whether a port is open from inside and outside the server covers the tools for that.
Docker's daemon has an ip setting that changes the default bind address, but it applies only to the default bridge network. A compose project creates its own network, so that setting does not protect it. The prefix on every line is the reliable way.
One reverse proxy, TLS in one place
With everything on loopback, one reverse proxy is the front door for the whole stack. Caddy is the shortest path, because it requests and renews certificates on its own. Install it from the project's repository, as of September 2026:
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpg
sudo chmod o+r /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install -y caddyGive each app a subdomain pointing at the VPS, then write /etc/caddy/Caddyfile:
sonarr.example.com {
reverse_proxy 127.0.0.1:8989
}
radarr.example.com {
reverse_proxy 127.0.0.1:7878
}
prowlarr.example.com {
reverse_proxy 127.0.0.1:9696
}
jellyfin.example.com {
reverse_proxy 127.0.0.1:8096
}
requests.example.com {
reverse_proxy 127.0.0.1:5055
}sudo ufw allow 80,443/tcp
sudo systemctl reload caddy
curl -I https://sonarr.example.comThe curl should return a 200, or a 302 to the login page if Sonarr's authentication is set to Forms. A 502 means Caddy could not connect to 127.0.0.1:8989, so the container is down or its ports: line does not match. Subdomains are the safer choice over paths like /sonarr: each arr app would need its URL Base setting changed for a path to work, and Seerr does not support a base URL at all, so it only works on a subdomain. Caddy fetches one certificate per hostname automatically. A single wildcard certificate is possible through the DNS challenge, but that needs a Caddy build with your DNS provider's plugin. Either way, TLS is configured in one file and terminated by one process. If you would rather use nginx or Traefik, the reverse proxy comparison explains what each one costs you in config.
Only two ports are now open to the internet: 80 and 443, both owned by Caddy. Everything the arr apps listen on stays behind it. That is the complete answer to "which ports do I open".
qBittorrent behind gluetun shares gluetun's ports
The usual way to keep torrent traffic inside a VPN on a VPS is network_mode: "service:gluetun" on the qBittorrent container. That line means qBittorrent has no network stack of its own. It runs inside gluetun's network namespace, so it shares gluetun's interfaces, its IP address, its routes and its ports. Three things follow from that.
First, the ports: block moves to gluetun. Docker refuses to create a container that combines network_mode: service: with its own ports:, and the error is conflicting options: port publishing and the container type network mode. The gluetun wiki states the rule directly: you access ports of connected containers by mapping them on the gluetun container.
services:
gluetun:
image: qmcgaw/gluetun
cap_add:
- NET_ADMIN
ports:
- "127.0.0.1:8080:8080" # qBittorrent's web UI, published here
qbittorrent:
image: lscr.io/linuxserver/qbittorrent:latest
network_mode: "service:gluetun"
environment:
- WEBUI_PORT=8080Second, Sonarr and Radarr must point at gluetun, not qbittorrent. The name qbittorrent is not on the compose network, because the container has no endpoint of its own, so Sonarr's download client test fails with Unable to connect to qBittorrent. Set the host to gluetun and the port to 8080 and the test passes. The same applies to any other container you attach to gluetun: they all share one set of ports, together with gluetun's own control server on 8000, so two attached apps that both default to 8080 collide and the second one fails to bind.
Third, do not publish 6881. A peer connecting to qBittorrent must arrive through the VPN tunnel, and gluetun's firewall drops inbound connections on the tunnel unless FIREWALL_VPN_INPUT_PORTS lists the port, or VPN_PORT_FORWARDING=on is set with a provider that supports it (Private Internet Access, ProtonVPN, Perfect Privacy and PrivateVPN as of September 2026). Putting 6881:6881 on gluetun's ports: would open it on the VPS's public IP, which is the address the VPN exists to hide. Without an inbound port qBittorrent still downloads, it just cannot accept connections that other peers start. The full setup, including the health check and what happens to attached containers when gluetun restarts, is in routing Docker traffic through a VPN with gluetun.
Why ufw does not protect a published Docker port
This is the trap that turns a typo into an exposed stack. ufw writes its rules into the INPUT chain. Docker publishes a port with a DNAT rule in the nat table and accepts the traffic in the FORWARD chain, and Docker's own documentation says so: traffic to a published port is diverted before it reaches the INPUT and OUTPUT chains that ufw uses. So ufw status can show 8989 as not allowed while curl http://203.0.113.10:8989 from outside returns Sonarr's login page. The firewall did nothing wrong. The packet never reached it.
The 127.0.0.1: prefix fixes this at the source, because a port bound to loopback is never reachable from the public interface, whatever the firewall says. The other fixes, filtering in the DOCKER-USER chain or disabling Docker's iptables management, are compared in why Docker ports bypass ufw and how to close them. If the difference between a listening port and a published port is still fuzzy, what ports are on Linux is the ten-minute version.
FAQ
What port does Sonarr use by default?
Sonarr listens on 8989. Radarr uses 7878, Lidarr 8686, Prowlarr 9696 and Bazarr 6767. These are the ports inside the container and also the ports the official images publish, checked against each project's documentation in September 2026. Change the host side of the mapping if you need to, but keep the container side at the default, because the app reads its own port from its config.xml and a container-side mismatch gives you a connection refused.
Do I need to open the arr stack ports in ufw on a VPS?
No. Open 80 and 443 for the reverse proxy and nothing else. Bind every app port to 127.0.0.1 in the compose file so it is only reachable from the VPS itself, and let the proxy forward to it. Adding ufw allow 8989 would publish Sonarr's login page to the internet, and leaving it out does not help either, because Docker publishes ports in a way that bypasses ufw unless the port is bound to loopback.
Why can Sonarr not reach qBittorrent when qBittorrent runs behind gluetun?
Because network_mode: "service:gluetun" gives qBittorrent no network identity of its own. It lives inside gluetun's network namespace, so the name qbittorrent does not resolve on the compose network and Sonarr reports Unable to connect to qBittorrent. In Sonarr's download client settings set the host to gluetun and the port to 8080. The ports: line for the web UI also belongs on the gluetun service, not on qBittorrent.
Should I publish qBittorrent's 6881 torrent port on a VPS?
Not when qBittorrent runs behind a VPN. Publishing 6881 on the host opens it on the VPS's public IP, which bypasses the tunnel for inbound peers. If your VPN provider supports port forwarding, set VPN_PORT_FORWARDING=on in gluetun and give qBittorrent the forwarded port as its listening port. If it does not, qBittorrent still downloads without an inbound port; it just accepts fewer connections.