Skip to main content

Run a 24/7 aria2 download server with Docker

A complete, accurate deployment you fully control: an aria2 engine in a small Alpine container, a proper aria2.conf, optional AriaNg WebUI, secure remote access, post-download automation and health checks. Works on any Linux host, Synology, QNAP, Unraid or a VPS.

Last reviewed 2026-09 · aria2 v1.37.0 · Docker Engine 24+ / Compose v2

Before you start

Why Docker, and what we build

Running aria2 in a container gives you an isolated, reproducible, always-on downloader you can update or move without touching the host. The stack has three independent parts:

1. Engine (aria2c)

The multi-protocol downloader, reading a mounted aria2.conf and exposing JSON-RPC on port 6800.

2. WebUI (optional)

AriaNg, or the online WebUI, talks to the engine over RPC. Fully replaceable.

3. Storage

Two mounts: /downloads (payload) and /config (session, logs, hooks) — both persist outside the container.

We build our own tiny image from alpine. That keeps the guide accurate and portable — no hidden env variables from a third-party image you can't inspect. (Community images exist if you prefer a shortcut; verify their variables against their own docs.)

The three files

Create a folder (e.g. /opt/aria2) and add these three files next to each other.

1. aria2.conf

aria2.conf
dir=/downloads
continue=true
file-allocation=falloc
max-concurrent-downloads=5
max-connection-per-server=16
min-split-size=10M
split=16

# --- RPC (required for any WebUI) ---
enable-rpc=true
rpc-listen-all=true
rpc-listen-port=6800
rpc-secret=CHANGE_ME_STRONG_SECRET
# needed by browser WebUIs; see security notes
rpc-allow-origin-all=true

# --- BitTorrent ---
bt-max-peers=128
# listen ports must match the ports published in docker-compose.yml
listen-port=6888
dht-listen-port=6888
enable-dht=true
enable-peer-exchange=true
bt-enable-lpd=true
seed-ratio=1.0
disk-cache=64M

# --- Session persistence (survives restarts) ---
save-session=/config/aria2.session
input-file=/config/aria2.session
save-session-interval=60
log=/config/aria2.log

2. Dockerfile

Dockerfile
FROM alpine:3.20
RUN apk add --no-cache aria2 ca-certificates tzdata bash
COPY aria2.conf /etc/aria2/aria2.conf
VOLUME ["/downloads", "/config"]
EXPOSE 6800 6888 6888/udp
# aria2 refuses to start if input-file is missing, so create the session file first
ENTRYPOINT ["sh", "-c", "touch /config/aria2.session && exec aria2c --conf-path=/etc/aria2/aria2.conf"]

3. docker-compose.yml

docker-compose.yml
services:
  aria2:
    build: .
    container_name: aria2
    restart: unless-stopped
    environment:
      - TZ=UTC
    volumes:
      - ./aria2-config:/config
      - ./downloads:/downloads
    ports:
      - "6800:6800"      # JSON-RPC (keep private!)
      - "6888:6888"      # BitTorrent TCP
      - "6888:6888/udp"  # BitTorrent DHT UDP

The engine writes aria2.session and aria2.log into ./aria2-config, so both live on the host and persist across restarts and image rebuilds.

Deploy in 5 minutes

mkdir -p /opt/aria2/aria2-config /opt/aria2/downloads
cd /opt/aria2
# save aria2.conf, Dockerfile and docker-compose.yml here

docker compose up -d --build      # build once, then start
docker compose ps                 # confirm it is running
docker compose logs -f aria2      # watch startup

Confirm the RPC endpoint answers (replace the secret):

curl -s http://127.0.0.1:6800/jsonrpc \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":"1","method":"aria2.getVersion","params":["token:CHANGE_ME_STRONG_SECRET"]}'

A JSON reply with a version field means the engine and secret are correct.

Configure & secure

Key aria2.conf settings in this stack
SettingWhy
rpc-secretRequired. Without it anyone who reaches port 6800 controls your downloads.
rpc-allow-origin-all=trueLets a browser WebUI (AriaNg, this site's WebUI) call the RPC. Only safe because the port stays private.
file-allocation=fallocReserves space quickly and reduces disk fragmentation (use none on filesystems that don't support it).
disk-cache=64MBatch writes in RAM — fewer micro-writes, longer disk life.
continue=trueResume partial downloads across restarts.
save-session / input-filePersist the queue; downloads resume automatically after a restart.

Never publish port 6800 to the internet. Keep it bound to the host/LAN and reach it through a VPN or an authenticated reverse proxy (see below).

Add a WebUI

Pick whichever fits your workflow:

  ariang:
    image: nginx:alpine
    container_name: ariang
    restart: unless-stopped
    volumes:
      - ./ariang:/usr/share/nginx/html:ro
    ports:
      - "6880:80"

In AriaNg's settings, set RPC host to your server, port 6800, and paste the rpc-secret.

NAS & platform notes

Linux / VPS / Raspberry Pi

Follow the 5-minute deploy. On a Pi, keep disk-cache modest and expect lower throughput.

Synology DSM 7+

Container Manager → Project → Create; paste the compose. Use real DSM paths for the mounts (e.g. /volume1/docker/aria2/config and /volume1/downloads).

QNAP / Unraid

Container Station → Create Application, or Unraid's Compose plugin. Map /config to an appdata path and /downloads to a share.

File ownership

If other apps (Plex/Jellyfin/Arr) must read the files, either set the container user or chown the download folder. Our Alpine image runs as root by default; add user: "1000:1000" to the service to match your host UID/GID.

Secure remote access (HTTPS reverse proxy)

To reach the WebUI over the internet, put an authenticated HTTPS proxy in front and never expose 6800 directly. Modern browsers also block unencrypted RPC from an HTTPS page (mixed content), so you need TLS + WebSocket forwarding.

Nginx: TLS + WebSocket RPC + basic auth
server {
    listen 443 ssl;
    server_name aria2.example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    auth_basic "aria2";
    auth_basic_user_file /etc/nginx/.htpasswd;

    location / {
        proxy_pass http://127.0.0.1:6880;      # AriaNg
        proxy_set_header Host $host;
    }

    location /jsonrpc {
        proxy_pass http://127.0.0.1:6800/jsonrpc;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_read_timeout 86400;              # keep WebSocket alive
    }
}

Layer security: HTTPS, an auth layer (basic auth or an identity proxy), a strong rpc-secret, and a firewall. A VPN (WireGuard/Tailscale) is the simplest way to avoid exposing the service at all.

Post-download automation

aria2 runs a command when a download finishes via on-download-complete. Add it to aria2.conf and mount the script:

# in aria2.conf
on-download-complete=/config/on-complete.sh
on-download-error=/config/on-error.sh

# /config/on-complete.sh  (chmod +x)
#!/bin/bash
GID="$1"; NUM_FILES="$2"; TARGET_PATH="$3"   # $3 is the first file's path
logger "aria2 finished $TARGET_PATH"
# Examples:
#   rclone move "$(dirname "$TARGET_PATH")" remote:downloads/
#   unzip -o "$TARGET_PATH" -d "$(dirname "$TARGET_PATH")"
#   curl -s "https://ntfy.sh/mytopic" -d "Download complete: $(basename "$TARGET_PATH")"

$1 = GID, $2 = number of files, $3 = path of the first file. Keep hooks fast and non-blocking; do the heavy work in the background.

Health & monitoring

    healthcheck:
      test: ["CMD-SHELL", "aria2c --version >/dev/null && wget -qO- http://127.0.0.1:6800/jsonrpc --post-data='{\"jsonrpc\":\"2.0\",\"id\":\"h\",\"method\":\"aria2.getVersion\",\"params\":[\"token:CHANGE_ME_STRONG_SECRET\"]}' >/dev/null"]
      interval: 60s
      timeout: 10s
      retries: 3

Troubleshooting

WebUI says "Disconnected" / RPC fails

Check three things: the container is running (docker compose ps), port 6800 is mapped, and the secret matches rpc-secret. If the WebUI loads over HTTPS, its RPC calls must also be HTTPS/WSS (mixed-content rule) — use the reverse proxy above.

Torrent shows 0 peers / DHT 0 nodes

Map and forward one port for TCP and UDP (we use 6888), and add live trackers with the tracker list or bt-tracker=… in the config. DHT bootstraps once it can reach the network.

Permission denied writing downloads

The container writes as its user (root by default). Either set user: "1000:1000" to match the host, or chown the ./downloads folder so the container user can write to it.

Downloads restart from 0 after a reboot

Session persistence is not configured. Ensure save-session, input-file, and save-session-interval point at a mounted path (here /config/aria2.session) and that continue=true.

Port 6800 already in use / container won't start

Another aria2 instance or app owns 6800 on the host. Change the host mapping (e.g. "6801:6800") or stop the other process.

FAQ

Do I need a WebUI container?

No. AriaNg, this site's online WebUI, or any RPC client can talk to the engine directly. The WebUI is optional decoration on top of the RPC API.

How do I update aria2?

Rebuild the image when a new Alpine package is available: docker compose build --no-cache && docker compose up -d. Your config, session and downloads persist in the mounts.

Is a community image like p3terx/aria2-pro fine?

Yes if you validate its documented variables. This guide builds its own minimal image so the configuration is explicit and portable, but a maintained community image is a valid shortcut.