Skip to main content

Reverse Proxy Configuration

Required for Remote Access

By default there is no authentication or authorization. You must use a reverse proxy with authentication if accessing from outside your local network. Exposing the server directly to the internet allows anyone to access, modify, and delete all your data. Built-in Authentication & RBAC (-auth) adds per-user accounts and roles, but it complements -- not replaces -- the reverse proxy in front of the server.

Network Restriction

At minimum, ensure Mahresources only binds to localhost:

# In your .env or command line
BIND_ADDRESS=127.0.0.1:8181

This prevents direct external access even if your firewall is misconfigured.

Nginx with Basic Authentication

Install Nginx and Create Password File

# Install nginx and apache2-utils (for htpasswd)
sudo apt install nginx apache2-utils

# Create password file
sudo htpasswd -c /etc/nginx/.htpasswd yourusername

Nginx Configuration

Create /etc/nginx/sites-available/mahresources:

server {
listen 80;
server_name mahresources.example.com;

# Redirect HTTP to HTTPS
return 301 https://$server_name$request_uri;
}

server {
listen 443 ssl http2;
server_name mahresources.example.com;

# SSL certificates (use Let's Encrypt)
ssl_certificate /etc/letsencrypt/live/mahresources.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mahresources.example.com/privkey.pem;

# Basic authentication
auth_basic "Mahresources";
auth_basic_user_file /etc/nginx/.htpasswd;

# Body size: must cover -max-upload-size (2 GiB default) and
# -max-import-size (10 GiB default)
client_max_body_size 10G;

location / {
proxy_pass http://127.0.0.1:8181;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

# Timeouts for large file uploads
proxy_connect_timeout 300;
proxy_send_timeout 300;
proxy_read_timeout 300;
}
}

Enable the site:

sudo ln -s /etc/nginx/sites-available/mahresources /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Basic auth at the proxy blocks the mr CLI and every API client

The mr CLI and the JSON API authenticate with Authorization: Bearer <token>, and a request carrying a Bearer value does not satisfy nginx, Caddy or Traefik basic auth, which want Authorization: Basic ... in that same header. There is no way to send both.

Use the built-in Authentication and RBAC with -auth instead, or exempt /v1/ from auth_basic and enable -auth so those requests are still authenticated.

The same applies to the Caddy and Traefik examples below, which put basic auth in front of every path as well.

Caddy

Caddy handles HTTPS certificates automatically.

Caddyfile

mahresources.example.com {
# Basic authentication
basicauth /* {
yourusername $2a$14$hashedpasswordhere
}

# Reverse proxy to Mahresources
reverse_proxy localhost:8181 {
# Increase timeouts for large uploads
transport http {
response_header_timeout 300s
}
}

# Request body limit: must cover -max-upload-size (2 GiB default) and
# -max-import-size (10 GiB default)
request_body {
max_size 10GiB
}
}

Generate the password hash:

caddy hash-password

Traefik

Docker Compose with Traefik

services:
traefik:
image: traefik:v2.10
command:
- "--api.insecure=true"
- "--providers.docker=true"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
- "--certificatesresolvers.letsencrypt.acme.email=you@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- traefik-certs:/letsencrypt

mahresources:
build: . # build from local Dockerfile
volumes:
- ./data/db:/data/db
- ./data/files:/data/files
environment:
- DB_TYPE=SQLITE
- DB_DSN=/data/db/mahresources.db
- FILE_SAVE_PATH=/data/files
- BIND_ADDRESS=:8181
labels:
- "traefik.enable=true"
- "traefik.http.routers.mahresources.rule=Host(`mahresources.example.com`)"
- "traefik.http.routers.mahresources.entrypoints=websecure"
- "traefik.http.routers.mahresources.tls.certresolver=letsencrypt"
- "traefik.http.services.mahresources.loadbalancer.server.port=8181"
# Basic auth middleware
- "traefik.http.routers.mahresources.middlewares=mahresources-auth"
- "traefik.http.middlewares.mahresources-auth.basicauth.users=yourusername:$$apr1$$hashedpass"

volumes:
traefik-certs:

Generate the password hash for Traefik:

# Install htpasswd
sudo apt install apache2-utils

# Generate hash (note: escape $ as $$ in docker-compose)
htpasswd -nb yourusername yourpassword

SSE (Server-Sent Events) Configuration

The download queue and plugin job system use Server-Sent Events at /v1/jobs/events and /v1/download/events. Reverse proxies must disable response buffering for these endpoints, or SSE messages will be delayed until the buffer fills.

Nginx

Add a location block for the SSE endpoints:

location ~ ^/v1/(jobs|download)/events$ {
proxy_pass http://127.0.0.1:8181;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

# Disable buffering for SSE
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 86400s;
chunked_transfer_encoding off;
}

Caddy

Caddy passes SSE events without buffering by default. No extra configuration is needed.

Traefik

Declaring the middleware is not enough; a router has to attach it. Add a second router matching the two SSE paths and point it at the middleware:

labels:
- "traefik.http.middlewares.sse-buffering.buffering.maxResponseBodyBytes=0"
- "traefik.http.routers.mahresources-sse.rule=Host(`mahresources.example.com`) && (PathPrefix(`/v1/jobs/events`) || PathPrefix(`/v1/download/events`))"
- "traefik.http.routers.mahresources-sse.entrypoints=websecure"
- "traefik.http.routers.mahresources-sse.tls.certresolver=letsencrypt"
- "traefik.http.routers.mahresources-sse.service=mahresources"
- "traefik.http.routers.mahresources-sse.middlewares=mahresources-auth,sse-buffering"

Alternative Authentication Methods

OAuth2 Proxy

For more advanced authentication (Google, GitHub, etc.), consider using OAuth2 Proxy:

# Example with Google OAuth
oauth2-proxy \
--upstream=http://127.0.0.1:8181 \
--http-address=0.0.0.0:4180 \
--provider=google \
--client-id=123456789-abcdef.apps.googleusercontent.com \
--client-secret=GOCSPX-abc123def456ghi789 \
--email-domain=yourdomain.com

Authelia

For self-hosted SSO, Authelia provides two-factor authentication and user management.

Built-in Authentication Behind a Proxy

If you enable built-in Authentication & RBAC (-auth) behind a TLS-terminating proxy like the examples above, set two extra flags:

  • -session-cookie-secure -- marks the session cookie Secure so the browser only sends it over HTTPS. Set this whenever users reach Mahresources over TLS.
  • -trust-proxy-headers -- needed only if you use login rate-limiting (-login-max-attempts). Behind a proxy, the connection IP is the proxy, so per-IP throttling must read the real client IP from X-Forwarded-For. The Nginx, Caddy, and Traefik examples above all set that header.
Never trust proxy headers on a directly-exposed server

-trust-proxy-headers is off by default for a reason: if the server is reachable directly (not strictly behind a proxy that overwrites the header), a client can forge X-Forwarded-For to get a fresh apparent IP on every request and defeat per-IP throttling. Enable it only when a trusted proxy sets the header for you.

Security Checklist

  • Mahresources binds only to localhost (127.0.0.1:8181)
  • Reverse proxy requires authentication for all requests
  • HTTPS is enabled with valid certificates
  • Strong passwords are used for basic auth
  • Firewall blocks direct access to port 8181
  • Regular security updates are applied