Skip to content

Add a Security & Authentication page, recommending authentik in front of Maintainerr #135

Description

@enoch85

Summary

Add a dedicated Security & Authentication page, and recommend authentik as the supported way to put authentication in front of Maintainerr.

Background: authentication is one of the most requested features (feature board post #6, previously Maintainerr#623 and Maintainerr#990). Implementing authentication inside Maintainerr means owning sessions, cookies, password storage, and the CVE surface that comes with them. Delegating to an identity-aware reverse proxy avoids all of it, and it works today with no code changes.

Why this is a docs change and not a code change

I looked into whether Maintainerr could ship built-in authentik support. It cannot, and the reason is structural rather than a matter of effort: authentik has no plugin API or app-side SDK. It integrates only through standard protocols.

  • Proxy Provider - an authentik outpost sits in front of the app and authenticates before the request arrives. Requires nothing from the application. This is how authentik's own docs already cover Sonarr, Tautulli, Seerr, and Jellyfin.
  • OAuth2 / OIDC - would require Maintainerr to become a full OIDC relying party, which is implementing authentication, just with the credentials held elsewhere.
  • SAML / LDAP / RADIUS / SCIM - not applicable.

So the Proxy Provider path is both the only zero-code option and the one that matches how the rest of the *arr ecosystem documents authentik. Docs are the right deliverable.

Proposed page

New docs/Security.md, in the sidebar under Getting Started next to reverseproxy:

  • What an unauthenticated instance exposes, and why the masking on GET /api/settings is not a protection boundary
  • The core rule: treat the Maintainerr port as a secret, do not publish it, let only the reverse proxy reach it (including a docker-compose.yml example with no ports: mapping)
  • authentik setup, both Proxy mode and Forward auth (single application) mode
  • Alternatives, so the recommendation is not a hard requirement: Authelia, Tinyauth, Cloudflare Access, reverse proxy basic auth, or simply not exposing it and using a VPN
  • A credential-rotation checklist for anyone who has already been exposed
  • A note that the API key in Settings is not an inbound credential and protects nothing

The existing danger callout in docs/API.md would link here instead of standing alone.

Two Maintainerr-specific details worth documenting

These came out of reading the server source and are easy to get wrong:

  1. Server-Sent Events must not be buffered. Maintainerr streams live logs and task events from /api/logs/stream and /api/events/stream as text/event-stream, and does not send X-Accel-Buffering: no. Under nginx forward auth the Logs page and live task progress appear to hang unless proxy_buffering off is set for those paths. authentik's own proxy mode is fine here, since the outpost flushes immediately.

  2. Nothing needs to be exempted from authentication. Maintainerr has no inbound webhook receivers (all integrations are outbound), the UI and API are same-origin on a single port, and the Docker HEALTHCHECK runs inside the container so it bypasses the proxy entirely. The only path worth allowlisting is /api/health/*, and only if an external monitor needs it.

Upstream authentik docs

website/integrations/media/ in goauthentik/authentik currently has sonarr, tautulli, seerr, and jellyfin but no maintainerr. Worth contributing an Integrate with Maintainerr page there too, so the setup is discoverable from both sides.

Status

Drafts for both pages are written and ready to open as PRs. Filing this first so the approach can be agreed before review.


Drafts

Both pages in full, verbatim, ready to lift into PRs.

docs/Security.md (Maintainerr_docs)
---
id: security
slug: /security
description: Maintainerr has no built-in authentication. How to run it safely, and how to put authentik in front of it.
title: Security & Authentication
---

:::danger
Maintainerr has **no authentication of any kind**. Every API endpoint is
unauthenticated. Anyone who can reach the port can read your configuration, run
rules, and delete media. Never expose a Maintainerr instance directly to the
internet.
:::

## What an unauthenticated instance exposes

This is not theoretical. With plain network access to Maintainerr, a caller can:

- `GET /api/settings/database/download` - download the entire SQLite database,
  including your Plex token, Jellyfin/Emby API key, Radarr/Sonarr API keys,
  Seerr, Tautulli, TMDB, TVDB, and download-client credentials, all in plain
  text.
- `POST /api/collections/handle` and `POST /api/rules/:id/execute` - trigger
  collection handling, which **deletes media** from your library and your \*arr
  instances.
- `POST /api/settings/...` - repoint Maintainerr at a different Plex, Jellyfin,
  Emby, or \*arr server.
- `GET /api/logs/files/:file` - read the log files.

The settings API masks secrets in its JSON responses, but the database download
does not, so masking is not a protection boundary.

### Local network exposure counts as exposure

Maintainerr sends `Access-Control-Allow-Origin` reflecting whatever origin asks.
Combined with the lack of authentication, that means a web page you visit in a
browser on your LAN can read Maintainerr's API on `localhost` or a private
address and exfiltrate the response. Browsers are gradually restricting requests
from public sites to private networks, but that is not a defence you should rely
on today.

The practical rule: **treat the Maintainerr port as a secret**. Bind it to
loopback or an internal Docker network, and let only your reverse proxy reach
it.

## The recommended setup

Maintainerr is designed to sit behind something that authenticates for it. It
does not implement authentication itself, and there is no plan to. Put an
identity-aware reverse proxy in front, and do not publish the container port.

```mermaid
architecture-beta
    service client(server)[Client]
    service revprox(server)[Reverse Proxy]
    service outpost(server)[Outpost]
    service app(server)[Maintainerr]
    service idp(server)[Identity Provider]

    client:R -- L:revprox
    revprox:R -- L:outpost
    outpost:R -- L:app
    outpost:T -- B:idp
```

In `docker-compose.yml`, that means no `ports:` mapping on the Maintainerr
service at all. Your proxy reaches it over the shared Docker network:

```yaml
services:
  maintainerr:
    image: ghcr.io/maintainerr/maintainerr:latest
    container_name: maintainerr
    volumes:
      - ./data:/opt/data
    networks:
      - proxy
    restart: unless-stopped
    # Deliberately no "ports:" - only the reverse proxy should reach 6246.

networks:
  proxy:
    external: true
```

If you need direct access for troubleshooting, bind to loopback only
(`127.0.0.1:6246:6246`) rather than `6246:6246`.

## Authenticating with authentik

[authentik](https://goauthentik.io/) is our recommended option. Its **Proxy
Provider** authenticates requests before they reach Maintainerr, so Maintainerr
needs no configuration and no code changes. authentik supports two shapes:

| Mode                                | Use when                                                                        |
| ----------------------------------- | ------------------------------------------------------------------------------- |
| **Forward auth (single application)** | You already run nginx, SWAG, Traefik, or Caddy. Recommended for most setups. |
| **Proxy**                           | You want the authentik outpost itself to be the reverse proxy.                  |

Full step-by-step setup lives in the authentik documentation:
[Integrate with Maintainerr](https://integrations.goauthentik.io/media/maintainerr/).

### nginx forward auth

Add the authentik `auth_request` block to your existing Maintainerr server block
(see [Reverse Proxy](/reverseproxy) for the base configuration). Two Maintainerr
specifics matter:

```nginx
# Maintainerr streams live logs and events over Server-Sent Events. nginx must
# not buffer those responses or the Logs page and live task updates will stall.
location ~ ^/api/(logs|events)/stream {
    proxy_buffering off;
    proxy_cache off;
    proxy_read_timeout 24h;
    proxy_pass http://maintainerr:6246;

    auth_request     /outpost.goauthentik.io/auth/nginx;
    error_page       401 = @goauthentik_proxy_signin;
    auth_request_set $auth_cookie $upstream_http_set_cookie;
    add_header       Set-Cookie $auth_cookie;
}
```

The authentik outpost also emits fairly large response headers. If you see
`upstream sent too big header while reading response header from upstream`,
raise the buffers:

```nginx
proxy_buffers 8 16k;
proxy_buffer_size 32k;
```

### Traefik, Caddy, and Envoy

authentik publishes ready-made middleware snippets for each. Maintainerr needs
nothing beyond the standard configuration, other than making sure streaming
responses are not buffered. See
[authentik forward auth](https://docs.goauthentik.io/add-secure-apps/providers/proxy/forward_auth).

### Calling the API through authentik

Once the outpost is in front, scripts and automations authenticate to the
outpost rather than to Maintainerr:

- **Bearer**: send `Authorization: Bearer <token>` where the token is issued for
  the proxy provider.
- **Basic**: send the reserved username `goauthentik.io/token` with an app
  password as the password.

Persist the cookies the outpost returns, otherwise every request re-authenticates
against authentik and adds load.

Leave the health endpoints reachable without authentication if an external
monitor needs them. `/api/health/live` and `/api/health/ready` return no
sensitive data. Add them to the provider's **Unauthenticated Paths** field:

```
^/api/health(/.*)?$
```

:::warning
Paths on the **Unauthenticated Paths** allowlist bypass authentik entirely.
Never add anything under `/api/settings`, `/api/collections`, `/api/rules`, or
`/api/logs`.
:::

## Other options

authentik is the recommendation, not a requirement. Anything that terminates
authentication before Maintainerr works:

- [Authelia](https://www.authelia.com/) - forward auth, same shape as authentik.
- [Tinyauth](https://tinyauth.app/) - lighter weight, single-binary.
- [Cloudflare Access](https://www.cloudflare.com/zero-trust/products/access/) -
  no self-hosted identity provider needed.
- **Reverse proxy basic auth** - nginx `auth_basic`, Caddy `basic_auth`. Crude,
  but far better than nothing.
- **No public exposure at all** - reach Maintainerr over
  [Tailscale](https://tailscale.com/), WireGuard, or your existing VPN. This is
  the simplest safe answer and needs no proxy.

## Rotating credentials after exposure

If your instance was reachable without authentication, assume everything in it
leaked and rotate:

- Plex: sign out of all devices, then reconnect Maintainerr.
- Jellyfin/Emby: delete and reissue the API key.
- Radarr, Sonarr, Sportarr, Seerr, Tautulli, Tracearr: reissue each API key.
- TMDB and TVDB API keys.
- Download-client (qBittorrent) password.
- Any notification agent webhook URLs and SMTP credentials.

## About the "API key" in settings

The **API key** shown under Settings is not an authentication credential for
Maintainerr's own API. Setting or regenerating it does not protect any endpoint.
It exists for internal use only. Do not treat it as a security control.
website/integrations/media/maintainerr/index.md (goauthentik/authentik)
---
title: Integrate with Maintainerr
sidebar_label: Maintainerr
support_level: community
---

## What is Maintainerr?

> Maintainerr is a library maintenance tool for Plex, Jellyfin, and Emby. It builds rule-based collections of unwatched or stale media and can hand them off to Radarr, Sonarr, and Seerr for cleanup.
>
> -- https://maintainerr.info/

Maintainerr has no authentication of any kind. Every one of its API endpoints is unauthenticated, including one that downloads its full configuration database. This guide uses the authentik Proxy Provider to authenticate requests before they reach Maintainerr.

## Preparation

The following placeholders are used in this guide:

- `maintainerr.company` is the FQDN of the Maintainerr installation.
- `authentik.company` is the FQDN of the authentik installation.

:::info
This documentation lists only the settings that you need to change from their default values. Be aware that any changes other than those explicitly mentioned in this guide could cause issues accessing your application.
:::

:::danger Protect the Maintainerr backend
Maintainerr performs no authentication and no authorization. Anyone who can reach its port can download `GET /api/settings/database/download`, which contains the plaintext Plex token, media server API key, Radarr/Sonarr API keys, and every other credential the instance holds, and can trigger media deletion. Do not publish the Maintainerr container port. Only the authentik outpost or your reverse proxy should be able to reach it.
:::

## authentik configuration

To support the integration of Maintainerr with authentik, you need to create an application/provider pair in authentik and assign it to a proxy outpost.

### Create an application and provider

1. Log in to authentik as an administrator and open the authentik Admin interface.
2. Navigate to **Applications** > **Applications** and click **New Application** to open the application wizard.
    - **Application**: provide a descriptive name, an optional group for the type of application, the policy engine mode, and optional UI settings.
    - **Choose a Provider type**: select **Proxy Provider** as the provider type.
    - **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.
        - Set **Mode** to **Proxy**.
        - Set **External host** to `https://maintainerr.company`.
        - Set **Internal host** to the URL that the authentik proxy outpost uses to reach Maintainerr.
            - If Maintainerr and the authentik proxy outpost are both running in the same Docker deployment, set the value to `http://<maintainerr_container_name>:6246`.
            - If Maintainerr runs on a different server than the authentik proxy outpost, set the value to `http://maintainerr.company:6246`.
        - _(optional)_ If an external monitor needs to reach the Maintainerr health probes, set **Unauthenticated Paths** to `^/api/health(/.*)?$`. These endpoints return no sensitive data. Do not add any other path.
    - **Configure Bindings** _(optional)_: you can create a [binding](/docs/add-secure-apps/bindings-overview/) (policy, group, or user) to manage the listing and access to applications on a user's **Application Dashboard** page.

3. Click **Submit** to save the new application and provider.

:::caution
Maintainerr exposes no read-only surface. Every user who can reach it can change settings and delete media. Bind the application to a group of trusted administrators rather than to all users.
:::

### Configure proxy outpost

The proxy provider requires an authentik proxy outpost. If you do not already have a proxy outpost, follow the [outpost documentation](/docs/add-secure-apps/outposts/) to create and deploy one.

Add the Maintainerr application to a proxy outpost that will serve it:

1. Log in to authentik as an administrator and open the authentik Admin interface.
2. Navigate to **Applications** > **Outposts**.
3. Click the edit icon for the proxy outpost. This can be the built-in **authentik Embedded Outpost** or another proxy outpost.
4. Under **Available Applications**, select the Maintainerr application and move it to **Selected Applications**.
5. Click **Update** to save your changes.

## Maintainerr configuration

Maintainerr requires no configuration for this integration. It has no authentication settings to change and no external-authentication mode to enable.

The only change to make is on the deployment side: stop publishing the Maintainerr port so that the instance is reachable only through the outpost.

```yaml title="docker-compose.yml"
services:
    maintainerr:
        image: ghcr.io/maintainerr/maintainerr:latest
        container_name: maintainerr
        volumes:
            - ./data:/opt/data
        networks:
            - authentik
        restart: unless-stopped
        # No "ports:" mapping. Only the outpost should reach port 6246.

networks:
    authentik:
        external: true
```

Configure DNS or your reverse proxy so that requests for `https://maintainerr.company` are routed to the authentik proxy outpost. The authentik proxy outpost then forwards authenticated requests to Maintainerr through the **Internal host** configured on the proxy provider.

```mermaid
architecture-beta
    service client(server)[Client]
    service revprox(server)[Reverse Proxy]
    service outpost(server)[Outpost]
    service maintainerr(server)[Maintainerr]
    service auth(server)[authentik]

    client:R -- L:revprox
    revprox:R -- L:outpost
    outpost:R -- L:maintainerr
    outpost:T -- B:auth
```

### Forward auth

If you already run nginx, Traefik, or Caddy in front of Maintainerr, use **Forward auth (single application)** mode instead of **Proxy** mode and apply the matching [configuration template](/docs/add-secure-apps/providers/proxy/forward_auth).

Maintainerr streams live logs and task events as `text/event-stream` from `/api/logs/stream` and `/api/events/stream`, and does not send `X-Accel-Buffering: no`. Disable response buffering for those paths in your reverse proxy, or the Logs page and live task progress will appear to hang.

```nginx
location ~ ^/api/(logs|events)/stream {
    proxy_buffering off;
    proxy_cache off;
    proxy_read_timeout 24h;
    proxy_pass http://maintainerr:6246;

    auth_request     /outpost.goauthentik.io/auth/nginx;
    error_page       401 = @goauthentik_proxy_signin;
    auth_request_set $auth_cookie $upstream_http_set_cookie;
    add_header       Set-Cookie $auth_cookie;
}
```

### Programmatic API access

Maintainerr's own **API key** setting is not an inbound credential and protects nothing. To script against a Maintainerr instance behind the outpost, authenticate to the outpost instead, using either an `Authorization: Bearer` token issued for the proxy provider or HTTP Basic with the reserved username `goauthentik.io/token`. See [header authentication](/docs/add-secure-apps/providers/proxy/header_authentication).

## Configuration verification

To verify the login flow, open `https://maintainerr.company`. You should be redirected to authentik before the Maintainerr web interface is shown.

To verify that the backend is not reachable directly, request the settings endpoint against the Maintainerr container address. It should fail to connect rather than return JSON.

## Resources

- [Maintainerr documentation](https://docs.maintainerr.info/)
- [Maintainerr reverse proxy configuration](https://docs.maintainerr.info/reverseproxy)

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions