# NPM Migration & Authentik Configuration (January 2026)

This document details the migration from Synology's built-in reverse proxy to Nginx Proxy Manager (NPM) with Authentik SSO protection.

## Migration Summary

**Date**: January 31, 2026  
**Status**: Complete  
**Last Updated**: January 31, 2026 (Session 2)  
**Performed by**: OpenHands AI Agent

### What Changed

1. **Router Configuration**
   - Port 443 → 192.168.0.250:8443 (NPM HTTPS)
   - Port 80 → 192.168.0.250:8880 (NPM HTTP)

2. **NPM Container Ports**
   - HTTP: 8880 → 80 (internal)
   - HTTPS: 8443 → 443 (internal)
   - Admin: 81 → 81 (internal)

3. **Cleaned up duplicate .synology.me entries** (11 deleted)
4. **Created new .vish.gg equivalents** for services that only had .synology.me
5. **Added Cloudflare Origin Certificates** for thevish.io and crista.love domains
6. **Changed Cloudflare SSL mode** from "Full (strict)" to "Full" for thevish.io
7. **Fixed meet.thevish.io (Jitsi)**: 
   - Enabled Cloudflare proxy (was DNS-only)
   - Changed backend to HTTPS (port 5443 uses SSL internally)
   - Added WebSocket support for XMPP connections
8. **Fixed joplin.thevish.io**: Works correctly - `/login` accessible, root returns 400 (expected API behavior)

---

## Access Credentials

### NPM (Nginx Proxy Manager)

| Field | Value |
|-------|-------|
| URL | https://npm.vish.gg or http://192.168.0.250:81 (local) |
| Email | user@example.com |
| Password | REDACTED_NPM_PASSWORD |
| API Port | 81 |

> Note: npm.vish.gg shows "Not Secure" because the wildcard cert doesn't cover it. Access locally at http://192.168.0.250:81 for admin tasks.

### Authentik SSO

| Field | Value |
|-------|-------|
| URL | https://sso.vish.gg |
| Admin Username | akadmin |
| Recovery Command | `docker exec -it Authentik-SERVER ak create_recovery_key 10 akadmin` |
| Secret Key | RpRexcYo5HAzvb8UGBhznwhq17sa2HALAYdMN51LR1ZBg5iL |
| PostgreSQL Password | ANJXq7n70DFEgWE+gD1qKhY/cXgQDPpjAJeF+Huiac8= |

### Portainer

| Field | Value |
|-------|-------|
| URL | http://vishinator.synology.me:10000 |
| API Key | ptr_REDACTED_PORTAINER_TOKEN |
| NPM Endpoint ID | 443397 |

### Cloudflare API

| Field | Value |
|-------|-------|
| Token | REDACTED_CLOUDFLARE_TOKEN |
| vish.gg Zone ID | 4dbd15d096d71101b7c0c6362b307a66 |
| thevish.io Zone ID | 11681f1c93ca32f56a0c41973e02b6f9 |
| crista.love Zone ID | (not documented) |

---

## SSL Certificates

### Certificate Inventory

| ID | Domain | Type | Expires | Location |
|----|--------|------|---------|----------|
| 1 | `*.vish.gg`, `vish.gg` | Cloudflare Origin | 2041 | `/data/custom_ssl/npm-1/` |
| 2 | `*.thevish.io`, `thevish.io` | Cloudflare Origin | 2041-01-27 | `/data/custom_ssl/npm-2/` |
| 3 | `*.crista.love`, `crista.love` | Cloudflare Origin | 2041-01-21 | `/data/custom_ssl/npm-3/` |

### Cloudflare SSL Mode Settings

| Zone | SSL Mode | Notes |
|------|----------|-------|
| vish.gg | Full | Works with Origin CA |
| thevish.io | Full | Changed from Full (strict) on 2026-01-31 |
| crista.love | Full | Works with Origin CA |

---

## Proxy Host Inventory

### vish.gg Domains (20 total, SSL cert ID 1)

| Domain | Backend | Port | Authentik | Status |
|--------|---------|------|-----------|--------|
| actual.vish.gg | 192.168.0.250 | 8304 | ✅ Yes | ✅ Working |
| cal.vish.gg | 192.168.0.200 | 12852 | No | ✅ Working |
| dav.vish.gg | 192.168.0.250 | 8612 | No | ✅ Working |
| docs.vish.gg | 192.168.0.250 | 8777 | ✅ Yes | ✅ Working |
| gf.vish.gg | 192.168.0.210 | 3300 | ✅ Yes | ✅ Working |
| git.vish.gg | 192.168.0.250 | 3052 | No (own auth) | ✅ Working |
| mastodon.vish.gg | 192.168.0.154 | 3000 | No (public) | ✅ Working |
| mx.vish.gg | 192.168.0.154 | 8082 | No | ✅ Working |
| npm.vish.gg | 192.168.0.250 | 81 | ✅ Yes | ✅ Working |
| ntfy.vish.gg | 192.168.0.210 | 8081 | No (API access needed) | ✅ Working |
| ollama.vish.gg | 192.168.0.200 | 11434 | No | ✅ Working |
| ost.vish.gg | 192.168.0.250 | 8004 | No | ✅ Working |
| paperless.vish.gg | 192.168.0.250 | 8777 | ✅ Yes | ✅ Working |
| pw.vish.gg | 192.168.0.200 | 4080 | No (Vaultwarden) | ✅ Working |
| rackula.vish.gg | 192.168.0.250 | 3891 | No | ✅ Working |
| retro.vish.gg | 192.168.0.250 | 8025 | No | ⚠️ 403 (upstream issue) |
| rxv4access.vish.gg | 192.168.0.250 | 9751 | No | ✅ Working |
| rxv4download.vish.gg | 192.168.0.250 | 9753 | No | ✅ Working |
| sf.vish.gg | 192.168.0.250 | 8611 | No (Seafile) | ✅ Working |
| sso.vish.gg | 192.168.0.250 | 9000 | No (Authentik itself) | ✅ Working |

### thevish.io Domains (5 total, SSL cert ID 2)

| Domain | Backend | Port | Status | Notes |
|--------|---------|------|--------|-------|
| binterest.thevish.io | 192.168.0.210 | 21544 | ✅ Working | |
| hoarder.thevish.io | 192.168.0.210 | 3000 | ✅ Working | Returns 307 redirect |
| joplin.thevish.io | 192.168.0.200 | 22300 | ✅ Working | /login works, / returns 400 (expected for API) |
| matrix.thevish.io | 192.168.0.154 | 8081 | ✅ Working | |
| meet.thevish.io | 192.168.0.200 | 5443 | ✅ Working | HTTPS backend, WebSocket config added |

### crista.love Domains (3 total, SSL cert ID 3)

| Domain | Backend | Port | Status | Notes |
|--------|---------|------|--------|-------|
| crista.love | 192.168.0.100 | 28888 | ✅ Working | Academic portfolio site |
| cocalc.crista.love | 192.168.0.100 | 8080 | ❌ 502 | Backend service is down |
| mm.crista.love | 192.168.0.154 | 8065 | ✅ Working | Mattermost |

---

## Authentik Forward Auth Configuration

Services protected by Authentik use this NPM Advanced Configuration:

```nginx
# Authentik Forward Auth Configuration
proxy_buffers 8 16k;
proxy_buffer_size 32k;

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;

auth_request_set $authentik_username $upstream_http_x_authentik_username;
auth_request_set $authentik_groups $upstream_http_x_authentik_groups;
auth_request_set $authentik_email $upstream_http_x_authentik_email;
auth_request_set $authentik_name $upstream_http_x_authentik_name;
auth_request_set $authentik_uid $upstream_http_x_authentik_uid;

proxy_set_header X-authentik-username $authentik_username;
proxy_set_header X-authentik-groups $authentik_groups;
proxy_set_header X-authentik-email $authentik_email;
proxy_set_header X-authentik-name $authentik_name;
proxy_set_header X-authentik-uid $authentik_uid;

location /outpost.goauthentik.io {
    proxy_pass http://192.168.0.250:9000/outpost.goauthentik.io;
    proxy_set_header Host $host;
    proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
    add_header Set-Cookie $auth_cookie;
    auth_request_set $auth_cookie $upstream_http_set_cookie;
    proxy_pass_request_body off;
    proxy_set_header Content-Length "";
}

location @goauthentik_proxy_signin {
    internal;
    add_header Set-Cookie $auth_cookie;
    return 302 https://sso.vish.gg/outpost.goauthentik.io/start?rd=$scheme://$http_host$request_uri;
}
```

---

## Cloudflare DNS Configuration

### vish.gg Zone

All subdomains should be **Proxied** (orange cloud) and point to `YOUR_WAN_IP`.

Missing DNS records were added during migration:
- paperless.vish.gg
- ollama.vish.gg
- rxv4access.vish.gg
- rxv4download.vish.gg

### thevish.io Zone

All subdomains point to `YOUR_WAN_IP` and are proxied.

**Important**: SSL mode must be "Full" (not "Full strict") for Origin CA certs to work.

### crista.love Zone

Subdomains point to `YOUR_WAN_IP` and are proxied.

---

## Troubleshooting

### NPM Returns 500 Error
Check if Authentik outpost is accessible:
```bash
curl -I http://192.168.0.250:9000/outpost.goauthentik.io/auth/nginx
```

### Authentik Recovery
```bash
docker exec -it Authentik-SERVER ak create_recovery_key 10 akadmin
```
Then visit: `https://sso.vish.gg/recovery/use-token/<TOKEN>/`

### Check NPM Logs
Via Portainer or:
```bash
docker logs nginx-proxy-manager
```

### Test Domain Resolution
```bash
curl -sI -k https://domain.vish.gg | head -5
```

### 522 Error (Connection Timed Out)
- Check if Cloudflare can reach your origin (port 443 forwarded?)
- Verify SSL mode is "Full" not "Full (strict)" for Origin CA certs
- Check if backend service is running

### 525 Error (SSL Handshake Failed)
- Origin expects HTTPS but backend doesn't have SSL
- Check `forward_scheme` is set to `http` in NPM for internal services

### Host Shows "Offline" in NPM
- Config file may not be generated
- Re-save the host in NPM to regenerate config
- Or manually create config in `/data/nginx/proxy_host/{id}.conf`

---

## TODO / Known Issues

1. ~~**thevish.io domains**: Need SSL certificates~~ ✅ Fixed - Origin certs added
2. ~~**crista.love domains**: Need SSL certificates~~ ✅ Fixed - Origin certs added
3. ~~**Change NPM password**: Currently using default~~ ✅ Changed to REDACTED_NPM_PASSWORD
4. **retro.vish.gg**: Returns 403 - check upstream service
5. ~~**joplin.thevish.io**: Returns 400~~ ✅ Works correctly - /login accessible
6. ~~**meet.thevish.io**: DNS not proxied~~ ✅ Fixed - Enabled proxy, HTTPS backend, WebSocket support
7. **cocalc.crista.love**: Backend service (192.168.0.100:8080) is down
8. ~~**crista.love**: Verify correct backend~~ ✅ Working - Academic portfolio site

---

## Jitsi Meet (meet.thevish.io) WebSocket Configuration

Jitsi requires special WebSocket handling for XMPP connections. The NPM config at `/data/nginx/proxy_host/18.conf` includes:

```nginx
# meet.thevish.io - Jitsi Meet with WebSocket support
map $scheme $hsts_header {
    https   "max-age=63072000; preload";
}

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
  set $forward_scheme https;  # Jitsi uses HTTPS internally
  set $server         "192.168.0.200";
  set $port           5443;

  listen 80;
  listen 443 ssl;
  server_name meet.thevish.io;
  http2 on;

  ssl_certificate /data/custom_ssl/npm-2/fullchain.pem;
  ssl_certificate_key /data/custom_ssl/npm-2/privkey.pem;

  # XMPP WebSocket endpoint - critical for Jitsi
  location /xmpp-websocket {
    proxy_pass $forward_scheme://$server:$port/xmpp-websocket;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;
  }

  # BOSH endpoint (fallback)
  location /http-bind {
    proxy_pass $forward_scheme://$server:$port/http-bind;
    proxy_buffering off;
    tcp_nodelay on;
  }
}
```

---

## Manual Config Creation

If NPM shows a host as "offline" and won't generate configs, create manually:

```bash
# Inside NPM container
cat > /data/nginx/proxy_host/{ID}.conf << 'EOF'
# {domain}
map $scheme $hsts_header {
    https   "max-age=63072000; preload";
}
server {
  set $forward_scheme http;
  set $server         "{backend_ip}";
  set $port           {backend_port};
  listen 80;
  listen 443 ssl;
  server_name {domain};
  http2 on;
  ssl_certificate /data/custom_ssl/npm-{cert_id}/fullchain.pem;
  ssl_certificate_key /data/custom_ssl/npm-{cert_id}/privkey.pem;
  include conf.d/include/block-exploits.conf;
  include conf.d/include/force-ssl.conf;
  access_log /data/logs/proxy-host-{ID}_access.log proxy;
  error_log /data/logs/proxy-host-{ID}_error.log warn;
  location / {
    include conf.d/include/proxy.conf;
  }
  include /data/nginx/custom/server_proxy[.]conf;
}
EOF

# Then reload nginx
nginx -t && nginx -s reload
```

---

## Related Documentation

- [Authentik SSO Setup](./authentik-sso.md)
- [Cloudflare DNS](./cloudflare-dns.md)
- [Service Documentation](../services/README.md)
