# Add Pocket ID Login to Homelab Apps with Tinyauth and Nginx Proxy Manager

Put an existing web app behind Pocket ID login using native Tinyauth and Nginx Proxy Manager, with a reusable setup for your other homelab apps.

Author: Ruhani Rabin
Canonical: https://rabin.blog/pocket-id-login-homelab-apps-tinyauth-npm/
Published: 2026-11-01T00:00:00.000Z
Updated: 2026-11-01T00:00:00.000Z

I already had [Pocket ID handling my Proxmox login](https://rabin.blog/how-to-configure-pocket-id-passkey-login-for-proxmox-ve-with-oidc-groups/). Then I wanted the same login for Wastebin, my little paste-sharing app.

Wastebin doesn’t have native user authentication. I wanted to keep using it, so I added Tinyauth alongside my existing Nginx Proxy Manager installation. Pocket ID handles the login. NPM checks with Tinyauth before letting a request through to the app.

This guide gets one whole app behind login first. Allow roughly **20 to 30 minutes**, assuming Pocket ID, DNS, HTTPS and shell access already work.

Allow more time if you need to sort out DNS or certificates first.

## TL;DR

* Run Tinyauth as its own systemd service beside a native NPM installation.
* Register one Tinyauth client in Pocket ID and restrict it to your chosen group.
* Give Tinyauth an HTTPS hostname, then add forward authentication to an existing NPM app host.
* Test with an unauthenticated browser before protecting more apps.

For Wastebin’s extra requirement, I wrote a [separate guide for authenticated creation and public paste links](https://rabin.blog/wastebin-pocket-id-login-public-pastes/).

## What you need

My working setup uses Debian 13 on an x86_64 Proxmox LXC, native NPM and Tinyauth v5.2.0.

**NPM and Tinyauth run as independent services in the same LXC**. Because, I am just lazy and I did not wanted to manage another LXC.

There is no Docker installation involved.

You need:

* A working Pocket ID installation and a passkey you can use.
* Native NPM already proxying your chosen app successfully.
* Root shell access to the NPM machine, plus `curl` and `logrotate`.
* A snapshot or backup of that machine and a copy of the app’s existing NPM Advanced (⚙️) configuration.

If your NPM runs in Docker, use the [upstream NPM deployment guide](https://tinyauth.app/docs/guides/nginx-proxy-manager/) instead of copying the localhost addresses below. Inside a container, localhost refers to that container.

Use these example names consistently, replacing them with yours:

| Purpose             | Example                             |
| ------------------- | ----------------------------------- |
| Existing Pocket ID  | `https://id.example.com`            |
| New Tinyauth portal | `https://auth.example.com`          |
| Existing app        | `https://app.example.com`           |
| Tinyauth listener   | `127.0.0.1:8085` on the NPM machine |

Keep the portal and app under the same parent domain for this guide. Both hostnames must resolve to NPM from the browsers using them, and NPM needs valid HTTPS certificates. Pocket ID must also be reachable over trusted HTTPS from Tinyauth.

## What Tinyauth adds

```mermaid
flowchart TD
    browser["🌐 Browser"] --> npm["🚦 NPM"]
    npm -->|Check session| auth["🔐 Tinyauth"]
    auth -->|Login| pocket["🗝️ Pocket ID"]
    npm -->|Approved requests| app["🏠 Web app"]
```

This is called **forward authentication**. NPM still proxies the app traffic. Tinyauth answers the authentication check.

The app doesn’t gain Pocket ID accounts or roles from this configuration. If it has its own login, that login still exists. If the app already supports OIDC properly, use its native integration, as I did with Proxmox.

Restrict the app’s backend port to NPM and any intended management network. Direct backend requests bypass the login check.

## 1. Install the native binary

Run the shell commands in this guide as **root, in Bash, on the NPM machine**. These instructions are for a fresh installation. Preserve any existing Tinyauth configuration.

Check the architecture and chosen port:

```bash
uname -m
ss -tlnp '( sport = :8085 )'
```

For the download below, the architecture should be `x86_64` and port 8085 should have no listener. My NPM backend already used port 3000, so I chose 8085 for Tinyauth.

Download the [v5.2.0 release](https://github.com/tinyauthapp/tinyauth/releases/tag/v5.2.0) into a temporary file, then install it:

```bash
curl -fL \
  https://github.com/tinyauthapp/tinyauth/releases/download/v5.2.0/tinyauth-amd64 \
  -o /tmp/tinyauth-amd64
install -o root -g root -m 755 \
  /tmp/tinyauth-amd64 /usr/local/bin/tinyauth
/usr/local/bin/tinyauth version
```

Expect `Version: v5.2.0`. This guide uses that tested release. Check upstream release notes and security updates before deploying it later.

## 2. Create the Pocket ID client

In Pocket ID, create a user group named `tinyauth_users` and add your account. Then open **Administration → OIDC Clients** and create a client:

| Setting             | Value                                                  |
| ------------------- | ------------------------------------------------------ |
| Name                | `Tinyauth`                                             |
| Callback URL        | `https://auth.example.com/api/oauth/callback/pocketid` |
| Client type         | Confidential, with a client secret                     |
| Allowed User Groups | `tinyauth_users`                                       |

Save the client ID and secret somewhere private. Select the group on the client’s Access tab too, so its members can use this client. Pocket ID documents this under [allowed user groups](https://pocket-id.org/docs/configuration/allowed-groups).

My tested setup left Pocket ID’s PKCE requirement disabled. That isn’t a Tinyauth limitation: [v5.2.0 sends an S256 challenge and verifier](https://github.com/tinyauthapp/tinyauth/blob/v5.2.0/internal/service/oauth_service.go). Requiring PKCE in Pocket ID is a separate setting to validate if you enable it.

## 3. Configure Tinyauth

Create the service account and directories:

```bash
id tinyauth >/dev/null 2>&1 || \
  useradd --system --user-group --home-dir /var/lib/tinyauth \
    --shell /usr/sbin/nologin tinyauth
install -d -o root -g tinyauth -m 710 /etc/tinyauth
install -d -o tinyauth -g tinyauth -m 700 /var/lib/tinyauth
install -d -o tinyauth -g tinyauth -m 750 /var/log/tinyauth
```

The `710` permission matters. Tinyauth needs to traverse `/etc/tinyauth` to read its secret file. Making that directory root-only prevents it from reaching the file, even when the file itself has the right owner.

Store the client secret without putting it in shell history:

```bash
read -r -s -p 'Pocket ID client secret: ' TINYAUTH_CLIENT_SECRET
echo
(umask 077; printf '%s' "$TINYAUTH_CLIENT_SECRET" > /etc/tinyauth/pocketid-secret)
unset TINYAUTH_CLIENT_SECRET
chown tinyauth:tinyauth /etc/tinyauth/pocketid-secret
chmod 600 /etc/tinyauth/pocketid-secret
```

Create `/etc/tinyauth/tinyauth.env` in your editor. Replace the example domains and `REPLACE_WITH_CLIENT_ID`. The secret stays in its separate file.

/etc/tinyauth/tinyauth.env

```ini
TINYAUTH_APPURL=https://auth.example.com
TINYAUTH_SERVER_ADDRESS=127.0.0.1
TINYAUTH_SERVER_PORT=8085
TINYAUTH_DATABASE_DRIVER=sqlite
TINYAUTH_DATABASE_PATH=/var/lib/tinyauth/tinyauth.db
TINYAUTH_AUTH_SECURECOOKIE=true
TINYAUTH_AUTH_TRUSTEDPROXIES=127.0.0.1/32
TINYAUTH_OIDC_PRIVATEKEYPATH=/var/lib/tinyauth/tinyauth_oidc_key
TINYAUTH_OIDC_PUBLICKEYPATH=/var/lib/tinyauth/tinyauth_oidc_key.pub
TINYAUTH_RESOURCES_PATH=/var/lib/tinyauth/resources
TINYAUTH_LOG_LEVEL=info
TINYAUTH_OAUTH_PROVIDERS_POCKETID_CLIENTID=REPLACE_WITH_CLIENT_ID
TINYAUTH_OAUTH_PROVIDERS_POCKETID_CLIENTSECRETFILE=/etc/tinyauth/pocketid-secret
TINYAUTH_OAUTH_PROVIDERS_POCKETID_AUTHURL=https://id.example.com/authorize
TINYAUTH_OAUTH_PROVIDERS_POCKETID_TOKENURL=https://id.example.com/api/oidc/token
TINYAUTH_OAUTH_PROVIDERS_POCKETID_USERINFOURL=https://id.example.com/api/oidc/userinfo
TINYAUTH_OAUTH_PROVIDERS_POCKETID_REDIRECTURL=https://auth.example.com/api/oauth/callback/pocketid
TINYAUTH_OAUTH_PROVIDERS_POCKETID_SCOPES="openid email profile groups"
TINYAUTH_OAUTH_PROVIDERS_POCKETID_NAME="Pocket ID"
```

These variables combine the [v5.2.0 configuration fields](https://github.com/tinyauthapp/tinyauth/blob/v5.2.0/internal/model/config.go) with the [Pocket ID provider settings](https://tinyauth.app/docs/guides/pocket-id/). There is no local password user in this setup.

Restrict the environment file and confirm the service account can read the secret:

```bash
chown root:root /etc/tinyauth/tinyauth.env
chmod 600 /etc/tinyauth/tinyauth.env
runuser -u tinyauth -- test -s /etc/tinyauth/pocketid-secret && \
  runuser -u tinyauth -- test -r /etc/tinyauth/pocketid-secret && \
  echo 'Secret exists and is readable'
```

Create `/etc/systemd/system/tinyauth.service`:

/etc/systemd/system/tinyauth.service

```ini
[Unit]
Description=Tinyauth authentication service
Wants=network-online.target
After=network-online.target


[Service]
Type=simple
User=tinyauth
Group=tinyauth
WorkingDirectory=/var/lib/tinyauth
EnvironmentFile=/etc/tinyauth/tinyauth.env
ExecStart=/usr/local/bin/tinyauth
Restart=on-failure
RestartSec=5
UMask=0077
NoNewPrivileges=true
PrivateTmp=true
StandardOutput=append:/var/log/tinyauth/tinyauth.log
StandardError=append:/var/log/tinyauth/tinyauth.log


[Install]
WantedBy=multi-user.target
```

The system service manager reads the root-owned environment file before starting the process as `tinyauth`. The database, keys and resources stay under `/var/lib/tinyauth`.

Create `/etc/logrotate.d/tinyauth` so file logs rotate daily, keeping seven rotations:

/etc/logrotate.d/tinyauth

```text
/var/log/tinyauth/*.log {
    daily
    rotate 7
    missingok
    notifempty
    copytruncate
    compress
    dateext
}
```

Check and start the service:

```bash
systemd-analyze verify /etc/systemd/system/tinyauth.service
systemctl daemon-reload
systemctl enable --now tinyauth
systemctl is-active tinyauth
curl -I http://127.0.0.1:8085
```

Expect `active` and an HTTP response from Tinyauth. This checks startup and connectivity. Browser login comes next.

## 4. Open the authentication portal

In NPM, create the proxy host `auth.example.com` with:

* Scheme: `http`
* Forward hostname: `127.0.0.1`
* Forward port: `8085`
* A valid certificate and Force SSL enabled

Disable **Block Common Exploits on this portal host**, as required by the [Tinyauth NPM instructions](https://tinyauth.app/docs/guides/nginx-proxy-manager/). Its redirect parameters can otherwise be blocked. Don’t add forward-auth rules to the portal itself.

Open `https://auth.example.com`, choose Pocket ID and sign in. After returning, Tinyauth should show your authenticated account. Get this working before changing the app host.

## 5. Protect your app in NPM

Keep the app host’s existing upstream and SSL settings. On a standard host without custom locations, merge this into its **Advanced** (Gear ⚙️) tab. Preserve unrelated settings and avoid duplicate directives or `/tinyauth` locations.

NPM → app.example.com → Advanced ⚙️

```nginx
auth_request /tinyauth;
auth_request_set $redirection_url $upstream_http_x_tinyauth_location;
error_page 401 403 =302 $redirection_url;


location = /tinyauth {
    internal;
    proxy_pass http://127.0.0.1:8085/api/auth/nginx;
    proxy_pass_request_body off;
    proxy_set_header Content-Length "";
    proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
    proxy_set_header X-Original-Method $request_method;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Real-IP $remote_addr;
}
```

This follows the server-level placement used in my native NPM setup. The [upstream example](https://tinyauth.app/docs/guides/nginx-proxy-manager/) puts the authentication directives inside an explicit app location instead. Don’t combine both layouts. Hosts with custom locations need a check of which locations inherit authentication. Preserve their app-specific proxy settings.

NPM asks the internal endpoint for permission, then sends approved requests to the existing backend. Point that check at localhost so it uses the local Tinyauth listener.

Save, then run on the NPM machine:

```bash
nginx -t
systemctl is-active npm
systemctl is-active tinyauth
```

My native installation uses `npm.service`. Use your installation’s service name if it differs. If NPM rejects the host or `nginx -t` fails, restore the previous Advanced (⚙️ icon) configuration before continuing.

## 6. Test without borrowing your own login

From your workstation, request the app without browser cookies:

```bash
curl -sS -o /dev/null -D - https://app.example.com/
```

Expect a `302` with a `Location` pointing to `https://auth.example.com/login?...`. Leave out `-L` so you can inspect the first response.

Then use a different browser where you haven’t logged in:

1. Open the app and confirm it sends you to Tinyauth.
2. Sign in through Pocket ID and confirm you return to the app.
3. Try the app’s normal action and a bookmarked inner page.
4. Open the same inner page in another unauthenticated browser and confirm it also requires login.

I realize, if i use two private windows in the same browser. It can share a PocketID session. I initially mistook that for public access while testing Wastebin. Firefox and Zen gave me independent sessions, and I verified the final workflow across browsers.

For another app, repeat the NPM configuration and tests. Keep the same Tinyauth instance and Pocket ID client. This policy can help designated apps can reach the endpoint. Different access groups per app need additional [Tinyauth access controls](https://tinyauth.app/docs/guides/access-controls/).

## If login fails

**`invalid_client` after Pocket ID consent:** check the client ID, then replace an incorrectly copied secret and restart Tinyauth. That fixed my failure. Use the hidden-input command from step 3, then `systemctl restart tinyauth`.

**Secret-file access errors:** repeat the service-account readability check. Check both the file ownership and the directory’s traverse permission.

**Redirect loops or a rejected callback:** compare `TINYAUTH_APPURL`, the provider’s redirect URL and the Pocket ID callback character for character. Check HTTPS, the shared parent domain, and the portal’s Block Common Exploits setting.

Read the app log with:

```bash
tail -n 50 /var/log/tinyauth/tinyauth.log
```

To undo app protection, restore its previous NPM Advanced (⚙️ icon) configuration and run `nginx -t`. That restores the app’s earlier access policy, which may be public. Leave Tinyauth running if other hosts use it. Back up `/etc/tinyauth` and `/var/lib/tinyauth` privately with the NPM machine. Verify startup after a planned reboot before relying on unattended recovery.

For an app you want entirely private, you’re done. For my pastebin, I also wanted people to read shared links without an account. That change is in [Wastebin with Pocket ID: login to create, public links to read](https://rabin.blog/wastebin-pocket-id-login-public-pastes/).
