Reverse Proxy Configuration
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
mr CLI and every API clientThe 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 cookieSecureso 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 fromX-Forwarded-For. The Nginx, Caddy, and Traefik examples above all set that header.
-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