SSD Nodes Learn Hosting plans →
Guides Matt ConnorBy Matt Connor

Connect Prowlarr, Sonarr and qBittorrent

Wire your arr stack together: indexers in Prowlarr, API keys into Sonarr and Radarr, then qBittorrent as the download client by container name.

What connecting Prowlarr, Sonarr and qBittorrent means

Connecting Prowlarr, Sonarr and qBittorrent is wiring done in a fixed order. Prowlarr holds your indexers and pushes them into Sonarr and Radarr using each app's API key. Sonarr and Radarr then search those indexers and hand the release they pick to qBittorrent, addressed by the qBittorrent container's hostname. Two settings cause almost every failure. Use localhost as the download client host and nothing ever downloads. Mount the download folder at a different path in each container and the download finishes but never reaches your library.

This guide assumes the containers are already up from a Docker Compose arr stack on a VPS, so you have four web UIs that work on their own and know nothing about each other. Every step below is something you click or type yourself. Nothing here runs automatically.

Confirm the compose service names first

The names you will type into the host fields are the service names from your compose.yaml, not the container names and not the image names. Read them back from Docker rather than from memory.

docker compose ps

The SERVICE column is the list you need. In most arr stacks it holds prowlarr, sonarr, radarr and qbittorrent, but a stack written by someone else may use qbit, qbittorrent-vpn or anything else. Whatever is printed there is the hostname other containers can resolve, because Docker's embedded DNS server publishes service names on any user-defined network. If your services sit on more than one network, two containers can only reach each other when they share at least one, which how Compose networks and service DNS work covers in detail.

Add indexers in Prowlarr

Open Prowlarr in a browser on port 9696 and go to Indexers, then Add Indexer. Type part of the name of the indexer you want, select it, and fill in the fields it asks for. A public indexer usually needs nothing beyond its URL. A private tracker needs an API key or a passkey from your account page on that tracker. Press Test, then Save.

Do this before touching anything else. Prowlarr pushes indexers outward, so an indexer added later still reaches Sonarr and Radarr on the next sync, but an app connected to an empty Prowlarr looks broken for no reason.

If an indexer tests fine in a browser and fails in Prowlarr with a Cloudflare challenge, the usual answer is FlareSolverr running as another container in the same stack, added under Settings, Indexers, as an indexer proxy and given a tag that matches the indexer. That is a separate service with its own setup, so treat a Cloudflare failure as unfinished work rather than a broken Prowlarr.

Connect Prowlarr to Sonarr and Radarr with the API key

Each arr app has one API key, and it lives in Settings, then General, in the Security block. Open Sonarr, go to Settings then General, and copy the API Key value. Radarr keeps it in exactly the same place. Treat the key as a password: anyone holding it can drive the app fully.

If Sonarr or Radarr will not let you log in at all, you cannot reach that page, and the way back in is resetting the Sonarr or Radarr login from config.xml. You can also read the key straight out of the config file, which is useful when the UI is unreachable:

docker compose exec sonarr cat /config/config.xml

The <ApiKey> element holds the same string the Settings page shows. Running commands inside a running container this way is described in opening a shell inside a Compose service.

Now open Prowlarr, go to Settings, then Apps, and press the plus button. Choose Sonarr. Four fields matter.

  • Sync Level: Full Sync keeps Sonarr's indexer list identical to Prowlarr's. Add and Remove Only leaves your manual edits in Sonarr alone.
  • Prowlarr Server: http://prowlarr:9696.
  • Sonarr Server: http://sonarr:8989.
  • API Key: the Sonarr key you copied.

Both URL fields are resolved from inside a container, never from your laptop, so the address in your browser bar is the wrong thing to paste. The Prowlarr Server field is the address Sonarr will use to call Prowlarr back. Save, then repeat the whole block for Radarr with http://radarr:7878 and Radarr's own API key.

Check the result in Sonarr under Settings, then Indexers. Your Prowlarr indexers should be listed there. They are owned by Prowlarr now: if Sync Level is Full Sync, an edit you make on the Sonarr side is overwritten on the next sync, so change indexer settings in Prowlarr instead.

Why the download client host is a service name and not localhost

Each service in your compose file runs in its own network namespace. That means localhost inside the Sonarr container is Sonarr itself, so localhost:8080 asks Sonarr for a qBittorrent that is not there. The connection is refused, or it reaches some unrelated service, and the download client test fails. Type the compose service name instead, and Docker's DNS resolves it to the qBittorrent container's address on the shared network.

The port follows the same logic. Use the container port, which is the right-hand side of the mapping in ports:. A line like 8090:8080 publishes qBittorrent on 8090 for your browser, but container to container traffic never passes through that published mapping, so Sonarr still connects to 8080. On the LinuxServer qBittorrent image the container port is whatever WEBUI_PORT is set to, so read that variable rather than assuming. Which port belongs to which app is laid out in the ports each arr stack service listens on.

Prove the path works before you touch the Sonarr settings page:

docker compose exec sonarr curl -s -o /dev/null -w '%{http_code}\n' http://qbittorrent:8080/

An HTTP status number, usually 200 or 401, means the name resolved and something answered. curl: (6) Could not resolve host: qbittorrent means the name is wrong or the two containers share no network. curl: (7) Failed to connect means the name resolved but nothing is listening on that port, so the port is wrong or qBittorrent is not running.

One case breaks the rule. If qBittorrent runs behind a VPN container with network_mode: service:gluetun, it has no network identity of its own, because it shares the Gluetun container's namespace. The host to enter is then gluetun, and the port is still the qBittorrent Web UI port, which Gluetun must publish. That arrangement has its own traps, covered in reaching a Gluetun bound container from the rest of your stack.

Add qBittorrent as the download client in Sonarr and Radarr

Log in to qBittorrent first. Recent builds do not ship a fixed default password. They print a temporary one at startup instead:

docker compose logs qbittorrent | grep -i password

The line names a temporary password for that session. Log in with the user admin and that value, then set a permanent username and password under Options, Web UI. A temporary password changes on every restart, so a download client that worked yesterday stops authenticating after a container update if you skip this.

While you are in qBittorrent, open Options, then Downloads, and set the default save path to the download directory your compose file mounts, normally /downloads. Also look at Default Torrent Management Mode. With Automatic selected, qBittorrent saves each torrent under a folder named after its category, which changes the finished path. That is fine as long as the same folder is visible to Sonarr, and it is a common reason a working setup breaks after someone flips the setting.

Now in Sonarr go to Settings, then Download Clients, press plus, and pick qBittorrent. Fill it in:

  • Host: qbittorrent, the compose service name.
  • Port: 8080, the container port.
  • Use SSL: off, since this traffic never leaves the Docker network.
  • Url Base: empty, unless you put qBittorrent behind a reverse proxy at a subpath.
  • Username and Password: the permanent qBittorrent credentials.
  • Category: tv-sonarr.

Press Test, then Save. If the test fails, open System, then Logs, in the same app: the log entry states the underlying error, which is more specific than the banner on the settings page. Repeat the same form in Radarr, changing only the category to radarr.

Give each app its own category. The category is how an arr app recognises its own downloads in a shared client. Point Sonarr and Radarr at the same category and each one sees the other's downloads in its queue, cannot match them to anything it tracks, and fills your queue with warnings. You do not need to create the category in qBittorrent first, because it appears once the first torrent arrives carrying that label.

Adding qBittorrent in Prowlarr under Settings, Download Clients is optional. It only affects grabs started from Prowlarr's own manual search. Automatic grabs always go through Sonarr or Radarr.

The download finishes but the file is never imported

This is the most common failure after the wiring is done, and the cause is always paths. When a torrent completes, the arr app asks qBittorrent where the files are. qBittorrent answers with a path as seen inside the qBittorrent container, for example /downloads/tv-sonarr/Some.Show.S01E01. Sonarr then looks for that exact string inside the Sonarr container. If the same host directory is mounted at /downloads in one container and /data/torrents in the other, the path Sonarr receives does not exist for Sonarr, so the import never starts.

Sonarr reports this as a health notice worded close to the following, though the exact text moves between versions:

You are using docker; download client qBittorrent places downloads in /downloads but this directory does not appear to exist inside the container. Review your remote path mappings and container volume settings.

The queue entry in Activity also stops with a warning icon, and hovering it shows the reason it could not be imported. Check both sides yourself:

docker compose exec qbittorrent ls -la /downloads
docker compose exec sonarr ls -la /downloads

The two listings must show the same files at the same path. When they do not, fix the volume lines so every service mounts the same host directory at the same container path. That single change also restores hardlinks, because a hardlink only works within one filesystem and one mount, which is the argument made in the arr stack folder layout that keeps hardlinks working.

Settings, Download Clients, Remote Path Mappings exists for the case where the download client genuinely runs on another machine. It rewrites the path string the client reports into a path the arr app can see. Using it to paper over mismatched mounts inside one Compose stack works, but it leaves the hardlink problem in place, so the import copies every file instead of linking it and your disk usage doubles.

Permission errors on import

The second failure looks similar and is not the same thing. The path exists on both sides, and the import still fails with a message about the path not being accessible, or about being unable to move or delete the file. Here qBittorrent wrote the finished files as its own user, and Sonarr runs as a different user, so Sonarr may read the directory but cannot write to it or remove the source after import.

Look at the numeric owner rather than the names, because the user names inside two different images rarely match:

docker compose exec sonarr ls -ln /downloads

The third and fourth columns are the owning UID and GID. Every container in the stack should use the same PUID and PGID, and those values should own the host directory behind the mount. A UMASK of 022 also blocks group writes, so a stack sharing files through a group usually wants 002. What those variables do, and why the numbers and not the names decide access, is explained in how PUID, PGID and UMASK control container file ownership.

Run one download end to end

Add a single series in Sonarr with a root folder set, then use Interactive Search on one episode and grab a release by hand. Automatic search hides which stage failed. Interactive search does not.

Watch it move. The release should appear in qBittorrent under the tv-sonarr category within seconds. It should appear in Sonarr under Activity, then Queue, with a progress figure that tracks qBittorrent. When the torrent completes, the queue entry should disappear and the episode should be listed under Activity, then History, as imported. A stage that never happens tells you where to look: nothing in qBittorrent points at the download client settings, a stuck queue entry points at paths or permissions.

Once this works, keep a copy of your compose file and your API keys somewhere outside the server, since rebuilding the stack means re-entering both. Holding keys in an env file rather than in the compose file itself is covered in keeping secrets out of your Compose file.

FAQ

Where is the API key in Sonarr and Radarr?

Settings, then General, in the Security block, on both apps. Each app has one key and it authenticates every API call, so treat it as a password. If the web UI is unreachable, the same value sits in the <ApiKey> element of /config/config.xml inside the container, readable with docker compose exec sonarr cat /config/config.xml.

Why does localhost not work as the download client host?

Every Compose service runs in its own network namespace, so localhost inside the Sonarr container means Sonarr, not the host machine and not qBittorrent. Enter the compose service name, normally qbittorrent, which Docker's embedded DNS resolves on the shared network. Use the container port as well, because container to container traffic does not pass through the published port mapping in ports:.

Why does the download finish in qBittorrent but never import?

qBittorrent reports the finished path as it sees it, and the arr app looks for that same string inside its own container. If the download directory is mounted at a different path in each container, the path does not exist on the arr side and the import never runs. Compare docker compose exec qbittorrent ls -la /downloads with the same command against sonarr. If the two listings differ, mount the same host directory at the same container path in both services.

Do Sonarr and Radarr need different qBittorrent categories?

Yes. The category is how each app recognises which downloads belong to it. Give Sonarr tv-sonarr and Radarr radarr. Share one category and each app sees the other's torrents in its queue, fails to match them against anything it tracks, and reports them as unmatched downloads until you clear them by hand.