A Claude Code skill and repair scripts for debugging and fixing Pangolin EE self-hosted tunnel installations.
Covers every issue discovered running Pangolin EE in production — empty dashboards, 404s, 502s, SSO errors, WireGuard tunnel failures, and more — with root cause explanations and copy-paste fixes.
| File | Purpose |
|---|---|
.claude/commands/pangolin-debug.md |
Claude Code /pangolin-debug skill |
scripts/fix_pangolin.cjs |
Comprehensive DB repair (9 fixes, runs inside Pangolin container) |
scripts/add_resource.cjs |
Register an HTTP resource in Pangolin's DB without using the API |
scripts/setup_pangolin.cjs |
Zero-API initial setup (admin user, org, role, site, newt) |
- Claude Code installed (
npm install -g @anthropic-ai/claude-code) - A terminal open in any project directory
Makes /pangolin-debug available in every Claude Code session on your machine.
# 1. Clone this repo
git clone https://github.com/TechButton/pangolin-debug-skill.git
cd pangolin-debug-skill
# 2. Create the global commands directory if it doesn't exist
mkdir -p ~/.claude/commands
# 3. Copy the skill file
cp .claude/commands/pangolin-debug.md ~/.claude/commands/pangolin-debug.mdDone. Open any Claude Code session and type /pangolin-debug.
Makes /pangolin-debug available only when Claude Code is running inside a specific project directory.
# 1. Clone this repo
git clone https://github.com/TechButton/pangolin-debug-skill.git
# 2. Copy the commands folder into your project
cp -r pangolin-debug-skill/.claude /path/to/your/project/Or if you're already inside your project:
mkdir -p .claude/commands
curl -o .claude/commands/pangolin-debug.md \
https://raw.githubusercontent.com/TechButton/pangolin-debug-skill/main/.claude/commands/pangolin-debug.mdmkdir -p ~/.claude/commands && curl -o ~/.claude/commands/pangolin-debug.md \
https://raw.githubusercontent.com/TechButton/pangolin-debug-skill/main/.claude/commands/pangolin-debug.mdOnce installed, open a Claude Code session in any directory and type /pangolin-debug followed by a description of your problem.
/pangolin-debug my dashboard shows no sites or resources after login
/pangolin-debug all my apps are returning 404
/pangolin-debug the newt tunnel won't connect
Claude will load the full knowledge base from the skill file and then diagnose and fix the issue — SSHing to your VPS, running DB queries, and applying targeted fixes.
Empty dashboard after setup:
/pangolin-debug I just set up Pangolin and the dashboard loads but shows
no sites or resources. I can log in fine but everything is empty.
→ Claude will check roleActions (the most common cause), grant all actions to the admin role, and verify the fix via the API.
404 on all resources:
/pangolin-debug every resource returns 404. The tunnel is connected
(I can see the site online in the DB) but no apps are reachable.
→ Claude will check resources.domainId for nulls, query the domains table, and link them.
SSO blocking apps it shouldn't:
/pangolin-debug Pangolin is showing "you're not allowed to access this resource"
for portainer even though I set sso=0 for it.
→ Claude will check the sso column (defaults to 1 in EE, must be written explicitly), fix the value, and explain the root cause.
WireGuard not connecting:
/pangolin-debug the newt agent connects but the WireGuard tunnel fails.
My domain is behind Cloudflare.
→ Claude will check your Gerbil config for the WireGuard endpoint, explain the CF proxy/UDP issue, and walk you through creating the DNS-only wg.<domain> record.
General debugging:
/pangolin-debug something is wrong with my Pangolin setup, I'm not sure what
→ Claude will run the full diagnostic script, dump the DB state, and identify any issues against the healthy-state checklist.
The scripts in scripts/ work standalone — no Claude Code needed. They run inside the Pangolin Docker container.
If your Pangolin dashboard is broken, run the repair script first:
# Copy script into the container
sudo docker cp scripts/fix_pangolin.cjs pangolin:/app/fix_pangolin.cjs
# Preview what would change (safe, no DB writes)
sudo docker exec pangolin node /app/fix_pangolin.cjs --dry-run
# Apply all fixes
sudo docker exec pangolin node /app/fix_pangolin.cjs| # | Fix | Symptom resolved |
|---|---|---|
| 1 | isShareableSite = 0 |
UI infinite redirect loop |
| 2 | enableProxy = 1 |
Tunnel not active on resources |
| 3 | enabled = 1 |
Resources / targets disabled |
| 4 | targets.method = http |
502 / TLS handshake failure on plain-HTTP backends |
| 5 | domains table |
404 on all resources (null domainId) |
| 6 | roleResources |
"Not allowed to access this resource" (SSO) |
| 7 | userResources |
Empty connections/settings in dashboard |
| 8 | roleActions + userActions |
Empty dashboard / 403 on every API call |
| 9 | termsAcceptedTimestamp |
Dashboard blocked after login |
Use this when you want to expose a new service through Pangolin without going through the dashboard or API:
sudo docker cp scripts/add_resource.cjs pangolin:/app/add_resource.cjs
sudo docker exec pangolin node /app/add_resource.cjs \
--site-id 1 \
--name myapp \
--subdomain myapp.example.com \
--http-port 65400 \
--target-port 8080 \
--target-host 192.168.1.100 \
[--method http|https] \
[--sso 1]| Argument | Required | Description |
|---|---|---|
--site-id |
Yes | ID of the Newt site (find with SELECT siteId FROM sites in the DB) |
--name |
Yes | Display name for the resource |
--subdomain |
Yes | Full domain for this resource (e.g. app.example.com) |
--http-port |
Yes | Proxy port Pangolin/Traefik will listen on (unique per resource — script exits if already taken) |
--target-port |
Yes | Port your service listens on behind the tunnel |
--target-host |
Yes | LAN IP of the host running the service |
--method |
No | http (default) or https if backend serves TLS |
--sso |
No | 1 to require Pangolin login gate, 0 (default) to bypass |
Note:
isShareableSiteis always set to0andtlsServerNameis always set toNULLfor HTTP resources. Both were previously incorrect (defaulting to1and the FQDN respectively), causing the dashboard redirect loop and incorrect TLS config on new resources.
Use when setting up Pangolin from scratch and the dashboard isn't accessible yet:
# 1. Get the setup token from Pangolin's startup logs
sudo docker logs pangolin 2>&1 | grep -i "setup"
# 2. Create a config file
cat > /tmp/setup-config.json << 'EOF'
{
"email": "admin@example.com",
"password": "your-secure-password",
"setupToken": "token-from-logs",
"orgId": "myorg",
"orgName": "My Organisation",
"siteName": "homeserver",
"newtId": "your-newt-id",
"newtSecret": "your-newt-secret"
}
EOF
# 3. Run setup
sudo docker cp scripts/setup_pangolin.cjs pangolin:/app/setup_pangolin.cjs
sudo docker cp /tmp/setup-config.json pangolin:/tmp/setup-config.json
sudo docker exec pangolin node /app/setup_pangolin.cjs /tmp/setup-config.json \
--secret-file /tmp/pangolin-newt-secret.txtThe script creates: admin user → org → admin role (with all actions) → site → newt record. Stdout emits SETUP_RESULT org_id=... site_id=... role_id=... newt_id=... (no secret). The raw newt secret is written to the path given by --secret-file (mode 0600). Without --secret-file the secret falls back to stdout with a warning — avoid this in logged environments.
Security notes:
- The config file is deleted automatically after setup. A warning is printed to stderr if deletion fails — delete it manually.
- New sites are created with
dockerSocketEnabled = 0. Enable Docker socket access explicitly in the Pangolin dashboard only if needed.
These are the most common sources of confusion when working with Pangolin EE's SQLite database directly:
| Issue | Detail |
|---|---|
user table |
Singular (not users). PK is id, but FK columns in child tables are always named userId |
roleActions |
Must be populated for all actions even on isAdmin=1 roles — empty = 403 on every API call |
sso column |
Defaults to 1 in EE — always write it explicitly in INSERT, never rely on the column default |
isShareableSite column |
Defaults to 1 in EE — always set to 0 for proxy resources; 1 + enableProxy=1 causes an infinite UI redirect loop |
domainId |
Resources with null domainId generate no Traefik routing — all requests return 404 |
session.id |
Stores a SHA256 hash of the raw cookie token (Lucia auth). Don't use DB values as cookies |
| CSRF | All mutating API calls require header X-CSRF-Token: x-csrf-protection |
sites.pubKey |
Must be NULL before Newt first connects — pre-seeding causes "Public key mismatch" |
| WireGuard + CF | WireGuard uses UDP; Cloudflare only proxies TCP. The WG endpoint must be a DNS-only record |
- Pangolin EE 1.16 or later
better-sqlite3andoslonode packages (present in all official Pangolin EE container images)- Node.js 18+ inside the container
- Pangolin — self-hosted tunnel server by Fossorial
- portless — homelab installer that uses these scripts for automated Pangolin setup
MIT