Troubleshooting
Where logs are recorded
SwarmOtter writes logs to stderr and to a file by default.
For a terminal run, logs appear in the terminal and are also recorded at:
$XDG_STATE_HOME/swarmotter/swarmotterd.log
If XDG_STATE_HOME is not set, the default is:
~/.local/state/swarmotter/swarmotterd.log
Override the file path when needed:
[logging]
file = true
file_path = "/var/log/swarmotter/swarmotterd.log"
For systemd deployments, logs are also available through the journal:
journalctl -u swarmotterd -f
missing field mode
Older builds required network.mode whenever [network] was present.
Current SwarmOtter accepts this DHCP/SLAAC-safe configuration:
[network]
required_interface = "br0"
That partial table defaults to strict containment with IPv6 enabled. Rebuild and rerun the current binary if the daemon still reports:
missing field `mode`
Web UI shows interface_missing
interface_missing means the daemon cannot see the configured interface in its
current network namespace.
Check the interface name on the same host or namespace where the daemon runs:
ip a show br0
Then confirm the config matches exactly:
[network]
required_interface = "br0"
Common causes:
- The daemon is running inside a container that does not have
br0. - The systemd unit runs in a different network namespace.
- The interface name is different from the host interface name.
- The daemon process lacks permission to create device-bound sockets when torrent networking starts.
- You are running an older binary after editing source code.
Web UI shows no_interface_address
The interface exists and is up, but SwarmOtter did not find a usable address.
Check:
ip a show br0
For IPv6, both settings must allow it:
[network]
allow_ipv6 = true
[torrent]
allow_ipv6 = true
Web UI shows dns_not_constrained
This means strict containment was configured to validate DNS but DNS containment could not be proven.
For interface-bound configurations, first check whether Linux can see DNS on that interface:
resolvectl dns br0
If this reports DNS servers for br0, current SwarmOtter builds allow torrent
hostname resolution through that constrained path.
If DNS cannot be proven constrained and you still set:
[network]
validate_dns = true
network health reports dns_not_constrained. Use a contained network
namespace, container network, or IP-literal trackers/bootstrap nodes when the
host cannot prove DNS is on the contained path.
IPv6 peers do not connect
Check all of the following:
[network]
allow_ipv6 = true
[torrent]
allow_ipv6 = true
Also confirm the interface has a usable IPv6 address:
ip -6 addr show dev br0
ip -6 route
If strict mode uses static source binding, required_source_ipv6 must match an
address assigned to the configured path.
.torrent drag-and-drop does nothing
Only .torrent files are accepted by drag-and-drop. Check the browser console
and daemon logs for upload errors, especially authentication failures and
api.max_request_body_bytes rejections.
Increase the upload limit if needed:
[api]
max_request_body_bytes = 33554432
API requests fail with unauthorized
When api.require_auth = true, include one of these headers:
Authorization: Bearer <token>
or:
X-SwarmOtter-Auth: <token>
The Web UI uses the same API routes as external clients.
If a trusted-LAN deployment should not require a token, set
api.require_auth = false in the mounted TOML configuration, or set
SWARMOTTER_API_REQUIRE_AUTH=false for the Compose deployment. Non-loopback
listeners log a warning because every reachable client can then control
SwarmOtter.
Chrome extension POST returns extension_origin_forbidden
Chrome Manifest V3 service workers are cross-origin clients. A privileged extension request normally carries both:
Origin: chrome-extension://<32-character-extension-id>
Sec-Fetch-Site: none
SwarmOtter accepts that Origin only with authenticated API mode and a valid API token. Configure:
[api]
require_auth = true
auth_token = "replace-with-a-long-random-token"
Then send the same token on the extension service worker’s request:
Authorization: Bearer <token>
or:
X-SwarmOtter-Auth: <token>
Also grant the exact SwarmOtter API origin in the extension manifest’s
host_permissions; HTTP and HTTPS permissions are separate. Do not try to set
Origin or Sec-Fetch-Site in extension code—the browser owns those headers.
Check the native JSON error code and message:
extension_origin_forbidden: authenticated mode is off, the token is absent or invalid, an authentication header is duplicated, or both supported token header forms were sent together.cross_origin_forbidden: Fetch Metadata, Origin, or Host failed the ordinary browser-origin policy.same-site/cross-site, foreign HTTP(S),null, opaque, malformed (including an invalid extension ID), and multi-value Origins remain intentionally rejected.
Setting only auth_token while require_auth = false does not enable extension
access. SwarmOtter does not broadly trust all installed extensions on an
unauthenticated listener.
Update helper health check reports connection resets
If deploy/update-swarmotter.sh reports repeated curl: (56) Recv failure: Connection reset by peer while checking http://127.0.0.1:9091/health, inspect
the service status and recent logs printed by the updater. Current release
images are also configuration-checked before the healthy stack is replaced.
To distinguish a daemon failure from host port filtering, verify whether the
daemon is healthy inside the shared Gluetun network namespace:
docker compose --env-file .env -f compose.yml exec swarmotter \
curl -fsS http://127.0.0.1:9091/health
If that succeeds but host curl http://127.0.0.1:9091/health fails, Gluetun is
blocking the published control-plane port. Set this in gluetun.env:
FIREWALL_INPUT_PORTS=9091
This opens the SwarmOtter API/Web UI port on Gluetun’s default interface. It does not expose torrent peer, tracker, DHT, webseed, or torrent DNS traffic outside the Gluetun VPN namespace.
Torrents are added but stay at 0 B/s
If torrents appear in the Web UI but stay at 0 B/s, check tracker status:
curl -sS http://127.0.0.1:9091/api/v1/torrents/<info_hash>/trackers
Check live per-torrent counters and engine diagnostics:
curl -sS http://127.0.0.1:9091/api/v1/torrents/<info_hash>/stats
Useful fields:
rate_down,rate_up: smoothed transfer rates in bytes/sec.active_peer_workers: current bounded peer download workers.known_peers: peers currently discovered by trackers, DHT, PEX, or direct input.peer_scheduler: live scheduler counts showing discovered, eligible, filtered, failed-backoff, no-progress-backoff, parallel candidate, worker limit, and serial-fallback state. Use this whenknown_peersis high butactive_peer_workersis low or zero.useful_peers: connected peers observed with pieces the torrent still needs and an unchoked or recently useful state.unchoked_peers: connected peers the engine has observed as unchoked.choked_peers: reserved for explicit choke-state telemetry; currentlynulluntil the engine records positive per-peer choke state.recent_peer_failures,recent_tracker_failures: recent failed peer sessions and tracker announce/scrape failures reported by the live engine.tracker_ok,tracker_message,last_announce: last tracker announce status from the live engine.tracker_last_ok_seconds_ago,dht_last_seen_seconds_ago,pex_last_seen_seconds_ago: freshness of the last successful tracker, DHT, and PEX discovery signals when live engine data is available.dht_discovery_ok,pex_discovery_ok: whether DHT or PEX discovery has succeeded recently in the live engine.
Tracker rows from /api/v1/torrents/<info_hash>/trackers report per-tracker
announce and scrape results. last_error/last_message remain announce-only.
scrape_status, last_scrape, nullable scrape_seeders/scrape_leechers/
scrape_downloads, and last_scrape_error describe scrape. A failed scrape
retains the previous successful counts. unsupported is expected for UDP and
HTTP(S) URLs whose final path does not begin with announce; it does not mean
UDP announce failed. If announce is not successful, compatibility seed/leech
counts fall back to retained scrape data.
Common causes:
- The torrent has no live seeders.
- The tracker hostnames cannot resolve under strict DNS containment.
- UDP tracker traffic is blocked by the network path.
- A supported HTTP(S) scrape is redirected to an HTTPS-to-HTTP downgrade, returns malformed/missing exact-key BEP 48 data, or exceeds the decoded cap.
- Only WebTorrent
wss://trackers are present; those are not BitTorrent TCP or UDP trackers.
In strict interface mode, hostname trackers and DHT bootstrap hostnames need
constrained DNS. On Linux, SwarmOtter accepts systemd-resolved link DNS for the
required interface, for example DNS servers shown by resolvectl dns br0.
Performance with large libraries (1,000+ torrents)
When managing large torrent libraries, monitor these indicators:
Symptoms of resource exhaustion
- API responses slow down significantly (multiple seconds).
- SSE/WebSocket subscribers receive
events_droppedlag notifications. - Torrents stay in
queuedstate despite available slots. - Daemon logs show repeated peer connection failures or tracker timeouts.
- High CPU usage from lock contention or excessive reconciliation.
Check file descriptor usage
Peer-session descriptors are bounded by a nonzero max_peers; payload files,
trackers, DHT, the shared listener, and the control plane add workload-specific
overhead. Check the daemon’s current limit and usage:
PID=$(pgrep swarmotterd)
cat /proc/$PID/limits | grep "Max open files"
ls /proc/$PID/fd | wc -l
If usage approaches the limit, increase it (see Deployment).
Check scheduler saturation
The stats endpoint reports scheduler pressure:
curl -sS http://127.0.0.1:9091/api/v1/stats | jq .scheduler
Key fields:
requested_downloadsvsgranted_downloads: if requested exceeds granted, the download slot cap is the bottleneck.requested_metadata_fetchesvsgranted_metadata_fetches: if requested exceeds granted, the metadata fetch slot cap is the bottleneck.peer_limit,peer_permits_in_use, andpeer_permits_available: the authoritative process-wide peer-session cap and current usage. Available isnullwhen unlimited.peer_sessions_denied: inbound sockets rejected before session start by an applicable global or per-torrent cap.peer_worker_budget_saturated(and legacy peer-worker budget fields): engine worker-pressure compatibility telemetry. It does not mean the process-wide peer connection cap is full; use the permit fields above for that decision.retry_backoff_torrents: high values indicate many torrents waiting for retry after transient failures.
Check event subscriber lag
If SSE or WebSocket clients report events_dropped, the broadcast buffer
(default 4,096) is overflowing. This happens during reconciliation bursts when
many torrents change state simultaneously. Clients should reconnect and
request a full state refresh after receiving a lag notification.
Reduce resource pressure
If performance degrades with large libraries:
- Lower
max_active_downloadsto reduce concurrent peer connections. - Lower
max_peers_per_torrentto reduce per-torrent resource usage. - Set a global
max_peerscap to bound total connection count. - Ensure file descriptor limits are sufficient (65,536+ for 1,000 torrents).
- Enable
autopilot.mode = "act"for automatic stalled-torrent mitigation.