Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
258 changes: 193 additions & 65 deletions docs/install/panel-security/tinyAuth-for-nginx.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,80 +4,107 @@ slug: /security/tinyauth-for-nginx
title: TinyAuth for Nginx
---

TinyAuth is the simplest way to protect your apps with a login screen
TinyAuth is a lightweight authentication middleware that protects your applications with a login screen.

This guide uses the Remnawave TinyAuth image based on TinyAuth v5.0.7:

```text
ghcr.io/maposia/remnawave-tinyauth:v5
```

:::caution Breaking changes in v5

TinyAuth v5 uses a new configuration format. Environment variables such as `PORT`, `APP_URL`, `USERS`, and `USERS_FILE` must be renamed before upgrading. See [Breaking changes: v4 to v5](#breaking-changes-v4-to-v5) before replacing a running v4 container.

:::

## Installation

Now it's time to add TinyAuth to your existing docker-compose.yml file or create a new one. If creating a new file, don't forget to add the `services:` section. The configuration can be as simple as this:
Add TinyAuth to your existing `docker-compose.yml` file. If you are creating a new file, include the `services:` section.

```yaml title="docker-compose.yml"
tinyauth:
services:
tinyauth:
container_name: tinyauth
hostname: tinyauth
image: ghcr.io/maposia/remnawave-tinyauth:latest
restart: always
image: ghcr.io/maposia/remnawave-tinyauth:v5
restart: unless-stopped
ports:
- '127.0.0.1:3002:3002'
- "127.0.0.1:3002:3002"
networks:
- remnawave-network
environment:
- PORT=3002
- APP_URL=https://tinyauth.example.com
- USERS=your-username-password-hash
- SECRET=some-random-32-chars-string
- TINYAUTH_SERVER_PORT=3002
- TINYAUTH_APPURL=https://tinyauth.example.com
- TINYAUTH_AUTH_USERS=your-username-password-hash
- TINYAUTH_AUTH_SECURECOOKIE=true
- TINYAUTH_DATABASE_PATH=/data/tinyauth.db
volumes:
- ./data:/data
# To get USERS and SECRET read below

```

## Configuring variables
Replace `tinyauth.example.com` with the domain where TinyAuth will be exposed.

To generate your first hash for user, use the following command
:::warning

Do not publish port `3002` on all interfaces. Binding it to `127.0.0.1` ensures that only Nginx on the same host can access TinyAuth directly.

:::

## Creating a user

Generate the first user with the v5 image:

```bash
docker run -it --rm ghcr.io/maposia/remnawave-tinyauth:latest user create --interactive
docker run -it --rm ghcr.io/maposia/remnawave-tinyauth:v5 user create --interactive
```

After running, you will be prompted to enter a username and password. You will also need to select <code>
output format</code>-<code>docker</code>
Enter a username and password, then select the Docker output format. The command returns a value in the following format:

After that, you will see a message that the user has been created and a <code>username:passwordHash</code> will appear which needs to be used in docker-compose.yml in the env <code>USERS</code>
```text
username:passwordHash
```

:::info
Use this value for `TINYAUTH_AUTH_USERS` in `docker-compose.yml`.

After you start the container, you can generate a hash for a user using the running tinyAuth container with the command.
After TinyAuth is running, you can create another user with:

```bash
docker exec -it tinyauth ./tinyauth user create --interactive
docker exec -it tinyauth tinyauth user create --interactive
```

:::

:::info
Multiple users can be provided as a comma-separated list:

Every configuration option that has a `FILE` equivalent (e.g. `USERS` and `USERS_FILE`), then the file can be used instead of the environment variable.
```yaml
environment:
- TINYAUTH_AUTH_USERS=user1:passwordHash1,user2:passwordHash2
```

`USERS=` comma separated list of tinyauth users.*(required)*
To load users from a file instead, use `TINYAUTH_AUTH_USERSFILE`:

`USERS_FILE=` A file containing a list of tinyauth users.
```yaml
environment:
- TINYAUTH_AUTH_USERSFILE=/run/secrets/tinyauth-users
```

All environment variables you can see on official documentation https://tinyauth.app/docs/reference/configuration
The old `SECRET` environment variable is not used by TinyAuth v5 and must be removed.

:::
See the [official configuration reference](https://tinyauth.app/docs/reference/configuration/) for all supported options.

To generate the <code>SECRET</code> environment variable using <code>openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c 32</code>.
## Configuring Nginx

## Configure

Next, you need to configure nginx.conf to protect the required path.
First, configure the TinyAuth upstream:

```nginx title="nginx.conf"
upstream tinyauth {
server 127.0.0.1:3002;
keepalive 16;
}
```

Expose the TinyAuth login interface on its own domain:

```nginx title="nginx.conf"
server {
server_name tinyauth.example.com;
listen 443 ssl;
Expand All @@ -97,9 +124,13 @@ server {
proxy_set_header X-Forwarded-Port $server_port;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
}
```

Add the following configuration to every application that should be protected:

```nginx title="nginx.conf"
server {
server_name panel.remnawave.com;
listen 443 ssl;
Expand All @@ -110,8 +141,9 @@ server {
ssl_trusted_certificate "/etc/nginx/ssl/panel.remnawave.com/fullchain.pem";

location / {
auth_request /tinyauth;
error_page 401 = @tinyauth_login;
auth_request /tinyauth;
auth_request_set $tinyauth_location $upstream_http_x_tinyauth_location;
error_page 401 403 =302 $tinyauth_location;
Comment on lines +144 to +146

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 If TinyAuth returns a 401 or 403 without the X-Tinyauth-Location response header (e.g., on a backend error or misconfiguration), $tinyauth_location will be an empty string. Nginx interprets an empty redirect target as a redirect to /, which immediately triggers another auth subrequest that fails again, producing a redirect loop. Adding a fallback error_page for 5xx codes avoids this.

Suggested change
auth_request /tinyauth;
auth_request_set $tinyauth_location $upstream_http_x_tinyauth_location;
error_page 401 403 =302 $tinyauth_location;
auth_request /tinyauth;
auth_request_set $tinyauth_location $upstream_http_x_tinyauth_location;
error_page 401 403 =302 $tinyauth_location;
# Fallback if TinyAuth does not return X-Tinyauth-Location.
error_page 500 502 503 504 /50x.html;


proxy_http_version 1.1;
proxy_pass http://remnawave;
Expand All @@ -121,57 +153,153 @@ server {
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;

# Preserve credentials intended for the protected application.
proxy_set_header Authorization $http_authorization;

# TinyAuth credentials must not be forwarded to the application.
proxy_set_header X-Api-Key "";

proxy_send_timeout 60s;
proxy_read_timeout 60s;
}

location /tinyauth {
proxy_pass http://tinyauth/api/auth/nginx;
location = /tinyauth {
internal;
proxy_pass http://tinyauth/api/auth/nginx;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
Comment on lines +167 to +171

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The keepalive 16 directive in the upstream block has no effect without proxy_http_version 1.1 and proxy_set_header Connection "" in the location block that proxies to it. Without these two directives, Nginx defaults to HTTP/1.0 for upstream requests, which always closes the connection after each request and never reuses the keepalive pool. As a result, every auth subrequest opens a fresh TCP connection to TinyAuth, making keepalive 16 a no-op.

Suggested change
location = /tinyauth {
internal;
proxy_pass http://tinyauth/api/auth/nginx;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
location = /tinyauth {
internal;
proxy_pass http://tinyauth/api/auth/nginx;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_http_version 1.1;
proxy_set_header Connection "";


proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header x-forwarded-proto $scheme;
proxy_set_header x-forwarded-host $http_host;
proxy_set_header x-forwarded-uri $request_uri;
}
# Always overwrite forwarded values instead of trusting client headers.
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Uri $request_uri;

location @tinyauth_login {
return 302 https://tinyauth.example.com/login?redirect_uri=$scheme://$http_host$request_uri;
# X-Api-Key authenticates the request in TinyAuth while the original
# Authorization header remains available to the protected application.
proxy_set_header X-Api-Key $http_x_api_key;
proxy_set_header Authorization $http_authorization;
}
}
```

TinyAuth v5 returns the appropriate login, unauthorized, or error URL through the `X-Tinyauth-Location` response header. Nginx stores it in `$tinyauth_location` and uses it for the redirect, so a hardcoded `/login?redirect_uri=...` location is no longer required.

#Make sure to replace the http://tinyauth.example.com with your own app URL
Validate and reload Nginx:

```bash
sudo nginx -t && sudo systemctl reload nginx
```

## Running the container
## Starting TinyAuth

After that, restart nginx and launch tinyAuth
Start or recreate the TinyAuth container:

```bash
docker compose down && docker compose up -d && docker compose logs -f
docker compose up -d --force-recreate tinyauth
docker compose logs -f tinyauth
```

:::warning
Open a protected application and confirm that Nginx redirects the browser to `https://tinyauth.example.com`.

Important: If you used tinyAuth before 01.12.2025 and updated, you have switched from version 3 to 4.
## Using `X-Api-Key`

:::
## Updating from v3 to v4
The Remnawave TinyAuth image supports TinyAuth credentials in a separate `X-Api-Key` header. This allows the original `Authorization` header to pass through to the protected application.

The header must contain HTTP Basic credentials:

```text
X-Api-Key: Basic base64(username:password)
```

For example:

```text
X-Api-Key: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
```

Starting from v4, Tinyauth is a stateful application that uses a SQLite database to store sessions. This change improves security. For Docker setups, include the following volume:
Example request:

```bash
services:
tinyauth:
volumes:
- ./data:/data
curl https://panel.remnawave.com/api/example \
-H "X-Api-Key: Basic $(printf 'username:password' | base64)" \
-H "Authorization: Bearer application-token"
Comment on lines +224 to +226

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 base64 on macOS (and some Linux defaults) wraps output at 76 characters. For credentials longer than ~55 bytes the encoded string will contain a newline, breaking the header value sent by curl. Using base64 | tr -d ' ' ensures a single-line output regardless of platform.

Suggested change
curl https://panel.remnawave.com/api/example \
-H "X-Api-Key: Basic $(printf 'username:password' | base64)" \
-H "Authorization: Bearer application-token"
curl https://panel.remnawave.com/api/example \
-H "X-Api-Key: Basic $(printf 'username:password' | base64 | tr -d '\n')" \
-H "Authorization: Bearer application-token"

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

```

## Issuing API-keys
If `X-Api-Key` is absent, TinyAuth falls back to standard Basic authentication from the `Authorization` header. If `X-Api-Key` is present but malformed or uses a different scheme, authentication is rejected without fallback.

:::info

You can use <code>Basic base64(username:password)</code> in the `X-Api-Key` header of your requests to the API.
## Breaking changes: v4 to v5

Example: `X-Api-Key: Basic dXNlcm5hbWU6cGFzc3dvcmQ=`
TinyAuth v5 introduces a unified configuration format. All TinyAuth environment variables now use the `TINYAUTH_` prefix and are grouped by section.

:::
| TinyAuth v4 | TinyAuth v5 |
| --- | --- |
| `PORT=3002` | `TINYAUTH_SERVER_PORT=3002` |
| `APP_URL=https://tinyauth.example.com` | `TINYAUTH_APPURL=https://tinyauth.example.com` |
| `USERS=...` | `TINYAUTH_AUTH_USERS=...` |
| `USERS_FILE=/path/to/users` | `TINYAUTH_AUTH_USERSFILE=/path/to/users` |
| `SECURE_COOKIE=true` | `TINYAUTH_AUTH_SECURECOOKIE=true` |

The complete mapping is available in the [official v4 to v5 migration guide](https://tinyauth.app/docs/breaking-updates/4-to-5/).

The Nginx integration also changes:

- Continue using `/api/auth/nginx` for the authentication subrequest.
- Read redirects from `X-Tinyauth-Location` instead of constructing the login URL manually.
- Mark the auth location as `internal`.
- Explicitly overwrite `X-Forwarded-Host` and the other forwarded headers.
- Explicitly pass `X-Api-Key` and `Authorization` to the auth subrequest.
- Clear `X-Api-Key` before proxying the request to the protected application.

### Upgrade procedure

1. Stop TinyAuth without deleting its data:

```bash
docker compose stop tinyauth
```

2. Back up the SQLite database and current Compose configuration:

```bash
cp -a ./data ./data-v4-backup
cp docker-compose.yml docker-compose.v4.yml
```

3. Rename all v4 environment variables to their v5 equivalents and remove `SECRET`.

4. Update the image:

```yaml
image: ghcr.io/maposia/remnawave-tinyauth:v5
```

5. Update the Nginx configuration and validate it:

```bash
sudo nginx -t
```

6. Pull and start TinyAuth v5:

```bash
docker compose pull tinyauth
docker compose up -d --force-recreate tinyauth
docker compose logs -f tinyauth
```

7. Verify browser login, cookie login, `X-Api-Key` authentication, and access to the protected application before removing the backup.

### Rollback

TinyAuth v5 applies additional SQLite migrations. Do not start TinyAuth v4 against a database that has already been migrated by v5.

To roll back, restore both the v4 Compose file and the v4 data backup:

```bash
docker compose stop tinyauth
rm -rf ./data
cp -a ./data-v4-backup ./data
cp docker-compose.v4.yml docker-compose.yml
docker compose up -d tinyauth
```