# Joplin Server

## Description

This page documents the current Joplin Server state on the Hetzner VPS.

Joplin Server is running on the VPS and is reachable through Nginx Proxy Manager when requests resolve to the VPS IP.

## Current configuration

- Compose path: `/home/dockeruser/docker/joplin-server`
- Files:
  - `/home/dockeruser/docker/joplin-server/docker-compose.yml`
  - `/home/dockeruser/docker/joplin-server/.env`
  - `/home/dockeruser/docker/joplin-server/README.md`
- Current runtime state: running
- `docker compose ps` in `/home/dockeruser/docker/joplin-server` shows:
  - `joplin-db`: healthy
  - `joplin-server`: up, bound to `127.0.0.1:22300`
- Intended public URL: `https://joplin.okit.pl`

Current DNS issue:

- `joplin.okit.pl` currently returns `CNAME okit.pl`, but no valid A or AAAA answer.
- Let's Encrypt failed with: `no valid A records found for joplin.okit.pl; no valid AAAA records found for joplin.okit.pl`.
- DNS needs to be fixed before normal public HTTPS works.

Fixes applied on 2026-04-15:

- Recreated the Joplin compose stack so `joplin-db` used the current Postgres 18 mount layout.
- Confirmed the Joplin `.env` password is no longer the placeholder.
- Joplin app started successfully and auto-migrated the database.
- Updated Nginx Proxy Manager's `proxy_host` database row for `joplin.okit.pl` to forward to `http://127.0.0.1:22300`, with websockets and block-exploits enabled.
- Manually updated active NPM config at `/home/dockeruser/docker/npm/data/nginx/proxy_host/1.conf` to use `127.0.0.1:22300`, because this NPM instance did not regenerate the config from SQLite on restart.
- Reloaded nginx successfully.

Successful tests on 2026-04-15:

```sh
curl -sS http://127.0.0.1:22300/api/ping -H 'Host: joplin.okit.pl'
# {"status":"ok","message":"Joplin Server is running"}

curl -sS http://127.0.0.1/api/ping -H 'Host: joplin.okit.pl'
# {"status":"ok","message":"Joplin Server is running"}

curl -sS --resolve joplin.okit.pl:80:135.181.153.108 http://joplin.okit.pl/api/ping
# {"status":"ok","message":"Joplin Server is running"}
```

Laptop-side diagnostics on 2026-04-15:

- Direct test to Joplin over Tailscale:
  - Command: `curl -v --connect-timeout 5 http://100.95.58.48:22300`
  - Result: connection refused.
  - Observed source address: `100.121.168.72`.
- TCP test to SSH over Tailscale:
  - Command: `nc -vz 100.95.58.48 22`
  - Result: connection succeeded.
- TCP test to Joplin over Tailscale:
  - Command: `nc -vz 100.95.58.48 22300`
  - Result: connection refused.
- Classical SSH test:
  - Command: `ssh -o BatchMode=yes -o ConnectTimeout=5 dockeruser@100.95.58.48 true`
  - Result: local SSH client refused to run because `/etc/ssh/ssh_config.d/20-systemd-ssh-proxy.conf` has bad owner or permissions.
- Classical SSH test with global config bypassed:
  - Command: `ssh -F /dev/null -o BatchMode=yes -o ConnectTimeout=5 dockeruser@100.95.58.48 true`
  - Result: `Permission denied (publickey,password).`
- Tailscale SSH wrapper test:
  - Command: `tailscale ssh dockeruser@100.95.58.48 true`
  - Result: host key verification failed because no ED25519 host key is known for `ubuntu-4gb-hel1-1.tailedf7b1.ts.net`.
- SSH tunnel was not established from the laptop during this test because SSH authentication or host-key verification was not completed.

## Known facts

Joplin Compose design:

- `app`
  - `image: joplin/server:latest`
  - `container_name: joplin-server`
  - `restart: unless-stopped`
  - `env_file: .env`
  - Binds only to localhost:
    - `127.0.0.1:22300:22300`
  - Depends on `db` with condition `service_healthy`
  - Network: `joplin-net`
- `db`
  - `image: postgres:18`
  - `container_name: joplin-db`
  - `restart: unless-stopped`
  - No exposed ports
  - Network: `joplin-net`
  - Volume:
    - `postgres_data:/var/lib/postgresql`
  - Healthcheck:
    - `pg_isready` using `POSTGRES_USER` and `POSTGRES_DB`
- Named volume:
  - `joplin_postgres_data`
- Named network:
  - `joplin-net`

Joplin `.env`:

```env
POSTGRES_PASSWORD=<set on VPS; not placeholder>
POSTGRES_USER=joplin
POSTGRES_DB=joplin
APP_PORT=22300
APP_BASE_URL=https://joplin.okit.pl
DB_CLIENT=pg
POSTGRES_HOST=db
POSTGRES_PORT=5432
```

Important notes from handoff:

- `POSTGRES_PASSWORD` has been changed from the original placeholder.
- Joplin is intentionally localhost-only.
- External access must go through Nginx Proxy Manager.
- Because Nginx Proxy Manager uses host networking, Nginx Proxy Manager should forward to `127.0.0.1:22300`.
- PostgreSQL is internal-only and should not be exposed publicly.

Required Nginx Proxy Manager proxy host for Joplin:

- Domain Names: `joplin.okit.pl`
- Scheme: `http`
- Forward Hostname / IP: `127.0.0.1`
- Forward Port: `22300`
- Websockets Support: enabled
- Block Common Exploits: enabled
- SSL:
  - Request Let's Encrypt certificate
  - Force SSL enabled
  - HTTP/2 enabled

DNS plan from handoff:

- Create A record:
  - `joplin.okit.pl -> 135.181.153.108`
- Optional AAAA record:
  - `joplin.okit.pl -> 2a01:4f9:c014:98f0::1`
- For normal Let's Encrypt through Nginx Proxy Manager, ports `80` and `443` must reach this VPS publicly.
- Public DNS should not point to the Tailscale IP if using standard Let's Encrypt HTTP validation.

Commands provided in handoff to start Joplin:

```sh
cd /home/dockeruser/docker/joplin-server
nano .env
# replace POSTGRES_PASSWORD
docker compose up -d
docker compose ps
docker compose logs -f app
```

Local tests on VPS after Joplin start:

```sh
curl -I http://127.0.0.1:22300
curl -I http://127.0.0.1:81
curl -I http://127.0.0.1:80
```

Public tests after DNS and Nginx Proxy Manager config:

```sh
dig joplin.okit.pl
curl -I https://joplin.okit.pl
```

## Unknown / needs clarification

- Whether Nginx Proxy Manager will preserve the manual generated-conf fix after future UI edits/restarts. The SQLite row is correct, but the active generated config did not update automatically during this fix.
- Whether `joplin.okit.pl` DNS has been created or fixed.
- Whether the optional AAAA record is intended.
- Whether Let's Encrypt certificate issuance has succeeded.
- Whether the laptop has a valid SSH key or password for `dockeruser@100.95.58.48`.
- Whether the Tailscale SSH host key for `ubuntu-4gb-hel1-1.tailedf7b1.ts.net` should be accepted on the laptop.
