Skip to content

Port update container for Gluetun to qBittorrent port syncing that updates the listening port on change.

License

Notifications You must be signed in to change notification settings

eslutz/Forwardarr

Forwardarr

Workflow Status Security Check Go Report Card License Release

A lightweight, production-ready Go application that automatically synchronizes port forwarding changes from Gluetun VPN to qBittorrent. Built with observability and reliability in mind.

Features

  • Automatic Port Synchronization: Monitors Gluetun's forwarded port file and updates qBittorrent instantly
  • Webhook Notifications: Send HTTP POST notifications when port changes occur for external integrations
  • Full Observability: Prometheus metrics for monitoring and alerting
  • Health & Readiness: Kubernetes-compatible health check endpoints
  • Efficient File Watching: Uses fsnotify for real-time file system events
  • Secure by Default: Runs as non-root user in Docker, minimal attack surface
  • Lightweight: ~15MB Docker image, minimal resource footprint
  • Production Ready: Automatic re-authentication, graceful error handling, fallback polling

Quick Start

Docker Compose

An example docker-compose.yml file is available at docker-compose.example.yml.

Docker CLI

docker run -d \
  --name forwardarr \
  -e GLUETUN_PORT_FILE=/tmp/gluetun/forwarded_port \
  -e TORRENT_CLIENT_URL=http://qbittorrent:8080 \
  -e TORRENT_CLIENT_USER=admin \
  -e TORRENT_CLIENT_PASSWORD=adminadmin \
  -v gluetun-data:/tmp/gluetun:ro \
  -p 9090:9090 \
  ghcr.io/eslutz/forwardarr:latest

Configuration

Forwardarr is configured via environment variables. For a complete, ready-to-use configuration file, see docs/.env.example.

Essential Settings

Variable Default Description
GLUETUN_PORT_FILE /tmp/gluetun/forwarded_port Path to Gluetun's forwarded port file
TORRENT_CLIENT_URL http://localhost:8080 qBittorrent WebUI address
TORRENT_CLIENT_USER admin qBittorrent username
TORRENT_CLIENT_PASSWORD adminadmin qBittorrent password
SYNC_INTERVAL 300 Polling interval in seconds (0 to disable)
METRICS_PORT 9090 HTTP server port for health/metrics
LOG_LEVEL info Log level: debug, info, warn, error
STARTUP_RETRY_DELAY 5 Base seconds between startup attempts (exponential backoff; attempts derived from timeout)
STARTUP_TIMEOUT 120 Overall startup deadline in seconds before exiting

Webhook Notifications (Optional)

Variable Default Description
WEBHOOK_URL Webhook endpoint (leave empty to disable)
WEBHOOK_TEMPLATE json Format: json, discord, slack, gotify
WEBHOOK_EVENTS port_changed Events to trigger webhooks
WEBHOOK_TIMEOUT 10 Request timeout in seconds

📋 See docs/.env.example for complete configuration with detailed comments and examples.

Architecture

┌─────────┐         ┌──────────┐         ┌────────────┐
│ Gluetun │ writes  │  Port    │ watched │            │
│   VPN   ├────────►│   File   ├────────►│ Forwardarr │
└─────────┘         └──────────┘         └─────┬──────┘
                                                │
                                                │ updates
                                                ▼
                                         ┌─────────────┐
                                         │ qBittorrent │
                                         └─────────────┘

How it works:

  1. Gluetun establishes a VPN connection with port forwarding
  2. Gluetun writes the forwarded port to a file
  3. Forwardarr watches this file for changes using fsnotify
  4. When the port changes, Forwardarr updates qBittorrent's listening port via API
  5. A fallback ticker ensures sync even if file events are missed (configurable, can be disabled)

Webhooks

Forwardarr can send HTTP POST notifications when port changes occur. This is useful for integrating with other services or triggering automation workflows.

Webhook Configuration

Configure webhooks via environment variables:

WEBHOOK_URL=http://your-server.com/webhook
WEBHOOK_TEMPLATE=json  # Options: json, discord, slack, gotify
WEBHOOK_EVENTS=port_changed  # Comma-separated event list
WEBHOOK_TIMEOUT=10  # Timeout in seconds

Webhook Templates

Forwardarr supports multiple webhook formats:

JSON (default) - Generic JSON payload

{
  "event": "port_changed",
  "timestamp": "2026-01-08T12:00:00Z",
  "old_port": 8080,
  "new_port": 9090,
  "message": "Port changed from 8080 to 9090"
}

Discord - Formatted for Discord webhooks with embeds

WEBHOOK_TEMPLATE=discord
WEBHOOK_URL=https://discord.com/api/webhooks/YOUR_WEBHOOK

Slack - Formatted for Slack webhooks with blocks

WEBHOOK_TEMPLATE=slack
WEBHOOK_URL=https://hooks.slack.com/services/YOUR_WEBHOOK

Gotify - Formatted for Gotify push notifications

WEBHOOK_TEMPLATE=gotify
WEBHOOK_URL=https://gotify.example.com/message?token=YOUR_TOKEN

Event Filtering

Control which events trigger webhooks using WEBHOOK_EVENTS:

WEBHOOK_EVENTS=port_changed          # Only port changes (default)

Currently supported events:

  • port_changed - Triggered when the forwarded port is successfully updated in qBittorrent

Note: Currently, port_changed is the only event type available. The event filtering system is designed for extensibility, allowing additional events to be added in future releases (such as sync_error, startup, or shutdown).

Webhook Security

  • Webhooks are sent with Content-Type: application/json
  • User-Agent is set to Forwardarr-Webhook/1.0
  • Consider using HTTPS URLs for webhook endpoints
  • Implement signature verification on your webhook receiver if needed
  • Webhook failures are logged but do not prevent port updates

HTTP Endpoints

Endpoint Purpose Response
GET /health Liveness probe 200 OK if running
GET /ready Readiness probe 200 OK if qBittorrent is reachable
GET /status Full diagnostics JSON status object
GET /metrics Prometheus metrics Metrics in OpenMetrics format

Endpoint Usage

  • /health: Configure this as a Liveness Probe. It indicates if the Forwardarr process is running. If this fails, the container should be restarted.
  • /ready: Configure this as a Readiness Probe. It indicates if Forwardarr can successfully communicate with qBittorrent. If this fails, the container should remain running but not receive traffic/work until the dependency recovers.
  • /status: Use this for manual debugging or external monitoring dashboards. It provides a JSON snapshot of the application's internal state, including version and connectivity status.
  • /metrics: Configure your Prometheus scraper to target this endpoint to collect application performance data.

Prometheus Metrics

Metric Type Description
forwardarr_info Gauge Build information (version, commit, date)
forwardarr_current_port Gauge Current forwarded port from Gluetun
forwardarr_sync_total Counter Total number of successful port syncs
forwardarr_sync_errors Counter Total number of failed sync attempts
forwardarr_last_sync_timestamp Gauge Unix timestamp of last successful sync

Example Prometheus Queries

# Current forwarded port
forwardarr_current_port

# Sync success rate (last 5m)
rate(forwardarr_sync_total[5m]) / (rate(forwardarr_sync_total[5m]) + rate(forwardarr_sync_errors[5m]))

# Time since last successful sync
time() - forwardarr_last_sync_timestamp

Grafana Dashboard

A pre-built Grafana dashboard is available at docs/dashboard.json. Import it into your Grafana instance to visualize:

  • Application version and build info
  • Current forwarded port with change history
  • Sync operation success rates and error counts
  • Go runtime metrics (memory, goroutines, CPU)
  • Time since last successful sync

Troubleshooting

Forwardarr can't connect to qBittorrent

  • Verify qBittorrent is accessible at the configured address
  • Check credentials are correct
  • Ensure network connectivity between containers
  • Check logs: docker logs forwardarr

Port not updating

  • Verify Gluetun is writing to the port file: cat /tmp/gluetun/forwarded_port
  • Check the port file path is correct in Forwardarr config
  • Ensure the volume mount is working: docker exec forwardarr cat /tmp/gluetun/forwarded_port
  • Increase log level to debug: LOG_LEVEL=debug

High resource usage

  • Increase SYNC_INTERVAL to reduce polling frequency
  • Check for excessive file system events in the watched directory

Contributing

Contributions are welcome! Please follow these guidelines when submitting changes.

Building from Source

# Clone the repository
git clone https://github.com/eslutz/forwardarr.git
cd forwardarr

# Install dependencies
go mod download

# Build binary
go build -o forwardarr ./cmd/forwardarr

# Build Docker image
docker build -t forwardarr .

Development

# Run tests
go test ./...

# Run tests with race detector and coverage
go test -race -coverprofile=coverage.out -covermode=atomic ./...

# View overall coverage
go tool cover -func=coverage.out | tail -1

# View filtered coverage (CI excludes the entrypoint file)
grep -v "cmd/forwardarr/main.go" coverage.out > coverage-filtered.out
go tool cover -func=coverage-filtered.out | tail -1

# Run linter
golangci-lint run

# Run locally
export GLUETUN_PORT_FILE=/path/to/port/file
export TORRENT_CLIENT_URL=http://localhost:8080
export TORRENT_CLIENT_USER=admin
export TORRENT_CLIENT_PASSWORD=adminadmin
go run ./cmd/forwardarr

CI enforces ≥60% coverage on the filtered profile (excluding the cmd/forwardarr/main.go entrypoint).

Before submitting a pull request:

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests if applicable
  5. Run linters and tests
  6. Submit a pull request

See our Pull Request Template for more details.

Security

Security is a top priority for this project. If you discover a security vulnerability, please follow responsible disclosure practices.

Reporting Vulnerabilities:

Please report security vulnerabilities through GitHub Security Advisories: https://github.com/eslutz/forwardarr/security/advisories/new

Alternatively, you can view our Security Policy for additional contact methods and guidelines.

Security Best Practices:

  • Keep your installation up to date with the latest releases
  • Use strong, unique passwords for qBittorrent credentials
  • Avoid exposing the metrics port to the public internet
  • Review and understand the volume mount permissions
  • Regularly monitor logs for suspicious activity

License

This project is licensed under the MIT License. See the LICENSE file for details.

You are free to use, modify, and distribute this software under the terms of the MIT License.

Acknowledgments

This project is built with and inspired by excellent open-source software:

  • Gluetun - VPN client with port forwarding support
  • qBittorrent - Feature-rich BitTorrent client
  • fsnotify - Cross-platform file system notifications for Go
  • Prometheus - Monitoring system and time series database

Special thanks to the open-source community for their contributions and support.

Related Projects

Other tools in the ecosystem:

  • Torarr - Tor SOCKS proxy container for the *arr stack with health monitoring
  • Unpackarr - Container-native archive extraction service for Sonarr, Radarr, and more

About

Port update container for Gluetun to qBittorrent port syncing that updates the listening port on change.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Sponsor this project

  •  

Packages

 
 
 

Contributors 3

  •  
  •  
  •