Fixing Planka WebSocket Issues with Tailscale

Fixing Planka WebSocket Issues with Tailscale Integration

Fixing Planka WebSocket Issues with Tailscale Integration

This writeup documents the complete solution for resolving WebSocket connection failures in Planka when accessed through Tailscale, including CORS configuration and proper proxy setup.

Problem

Planka was experiencing WebSocket connection failures with errors like:

WebSocket connection to 'wss://TAILSCALE_URL/socket.io/?__sails_io_sdk_version=1.2.1&__sails_io_sdk_platform=node&__sails_io_sdk_language=javascript&EIO=3&transport=websocket' failed

The application would load but remain stuck in a loading state due to failed WebSocket connections when accessed through Tailscale.

Root Cause

The issue was caused by:

  1. Incorrect BASE_URL configuration - pointing to wrong port

  2. CORS restrictions - Sails.js only allowing localhost origins

  3. Missing proxy trust configuration for Tailscale

Solution Overview

The fix involves configuring Planka's CORS settings to accept connections from any origin and setting up proper proxy trust for Tailscale, along with ensuring the BASE_URL matches the Tailscale endpoint.

Step-by-Step Solution

1. Configure Docker Compose Environment Variables

Edit /opt/planka/docker-compose.yml and ensure the environment section includes:

environment:
  - BASE_URL=https://TAILSCALE_URL
  - DATABASE_URL=postgresql://postgres@postgres/planka
  - SECRET_KEY=LONG_SECRET_KEY
  - TRUST_PROXY=true
  - SAILS_SECURITY_CORS_ALLOW_ORIGINS=*
  - SAILS_SECURITY_CORS_ALLOW_ANY_ORIGIN_WITH_CREDENTIALS_UNSAFE=true

Key changes:

  • BASE_URL must match your Tailscale hostname

  • TRUST_PROXY=true enables proxy support

  • SAILS_SECURITY_CORS_ALLOW_ORIGINS=* allows all origins

  • SAILS_SECURITY_CORS_ALLOW_ANY_ORIGIN_WITH_CREDENTIALS_UNSAFE=true enables credentials with wildcard origin

2. Apply Configuration Changes

# Update BASE_URL to match Tailscale URL
sudo sed -i '' 's|BASE_URL=http://localhost:3000|BASE_URL=https://TAILSCALE_URL|g' /opt/planka/docker-compose.yml

# Enable proxy trust
sudo sed -i '' 's|# - TRUST_PROXY=true|- TRUST_PROXY=true|g' /opt/planka/docker-compose.yml

# Add CORS configuration
sudo sed -i '' '/TRUST_PROXY=true/a\
      - SAILS_SECURITY_CORS_ALLOW_ORIGINS=*\
      - SAILS_SECURITY_CORS_ALLOW_ANY_ORIGIN_WITH_CREDENTIALS_UNSAFE=true' /opt/planka/docker-compose.yml

3. Configure Tailscale Serve

Set up Tailscale to serve Planka on HTTPS in background:

# Configure Tailscale serve to proxy to local Planka instance
nohup tailscale serve --https=443 http://localhost:3900 >/dev/null 2>&1 & disown

# Alternative: Use background flag (if supported)
tailscale serve --bg --https=443 http://localhost:3900

4. Restart Planka

Apply the configuration changes by restarting the containers:

cd /opt/planka
docker compose down
docker compose up -d

5. Verify Setup

Check that everything is running correctly:

# Check container status
docker ps | grep planka

# Verify local connectivity
curl -I http://localhost:3900

# Check Tailscale serve status
tailscale serve status

# Verify port binding
lsof -i :3900

Final Configuration

Docker Compose Environment Variables

environment:
  - BASE_URL=https://TAILSCALE_URL
  - DATABASE_URL=postgresql://postgres@postgres/planka
  - SECRET_KEY=LONG_SECRET_KEY
  - TRUST_PROXY=true
  - SAILS_SECURITY_CORS_ALLOW_ORIGINS=*
  - SAILS_SECURITY_CORS_ALLOW_ANY_ORIGIN_WITH_CREDENTIALS_UNSAFE=true

Tailscale Configuration

https://TAILSCALE_URL (tailnet only)
|-- / proxy http://localhost:3900

Port Configuration

Container: 0.0.0.0:3900->1337/tcp
Local Access: http://localhost:3900
Remote Access: https://TAILSCALE_URL

Access URLs

Local Access

  • URL: http://localhost:3900

  • Use case: Development and local testing

Remote Access (Tailscale)

  • URL: https://TAILSCALE_URL

  • Requirements:

    • Tailscale installed on client machine

    • Connected to same tailnet

    • HTTPS enabled automatically

Key Technical Details

CORS Configuration

The critical fix was enabling CORS for all origins in Sails.js:

// Equivalent to setting in config/security.js:
cors: {
  allRoutes: true,
  allowOrigins: '*',
  allowCredentials: true,
  allowAnyOriginWithCredentialsUnsafe: true,
}

WebSocket Support

Sails.js requires specific CORS configuration to allow WebSocket connections from external domains. The environment variables SAILS_SECURITY_CORS_ALLOW_ORIGINS=* and SAILS_SECURITY_CORS_ALLOW_ANY_ORIGIN_WITH_CREDENTIALS_UNSAFE=true override the default localhost-only restriction.

Tailscale Proxy

Tailscale serve acts as an HTTPS proxy, forwarding requests from the public Tailscale URL to the local Planka instance. The TRUST_PROXY=true setting tells Planka to trust the proxy headers from Tailscale.

Security Considerations

⚠️ Important: The configuration allowOrigins: '*' and allowAnyOriginWithCredentialsUnsafe: true allows connections from any domain. This is acceptable for Tailscale since:

  1. Tailscale provides network-level security through its VPN

  2. Access is restricted to your tailnet members

  3. The service is not exposed to the public internet

For production deployments on public networks, consider restricting allowOrigins to specific trusted domains.

Troubleshooting

Common Issues

WebSocket still failing after configuration:

# Restart Planka to ensure configuration is loaded
docker restart planka-planka-1

# Check that BASE_URL matches access URL
docker exec planka-planka-1 printenv BASE_URL

Tailscale serve not working:

# Kill existing serve processes
killall tailscale

# Restart serve with background flag
tailscale serve --bg --https=443 http://localhost:3900

# Verify status
tailscale serve status

Container unhealthy:

# Check detailed container status
docker inspect planka-planka-1 | grep -A 10 Health

# View detailed logs
docker logs planka-planka-1

Verification Commands

Use these commands to verify the complete setup:

# Container status
docker ps | grep planka

# Local connectivity
curl -s http://localhost:3900 | head -5

# Tailscale status
tailscale serve status

# Port verification
lsof -i :3900

# Environment check
docker exec planka-planka-1 printenv | grep -E "(BASE_URL|TRUST_PROXY|SAILS_SECURITY)"

Alternative Solutions

If the above solution doesn't work, consider these alternatives:

1. File-based Configuration Override

Create custom config/sockets.js and config/security.js files and mount them as volumes (requires Docker file sharing configuration on macOS).

2. Environment Variable Method

Use Sails.js environment variable overrides for more granular control:

- SAILS_SOCKETS_ONLYALLOW_ORIGINS=false
- SAILS_SECURITY_CORS_ALL_ROUTES=true

3. Reverse Proxy Headers

If using nginx or another reverse proxy, add WebSocket upgrade headers:

Upgrade: $http_upgrade
Connection: $connection_upgrade

Summary

The solution successfully resolves Planka WebSocket issues by:

  1. Configuring proper CORS settings via environment variables

  2. Setting correct BASE_URL to match Tailscale endpoint

  3. Enabling proxy trust for Tailscale forwarding

  4. Running Tailscale serve in background for persistent access

Final Result:

  • ✅ Local access: http://localhost:3900

  • ✅ Remote access: https://TAILSCALE_URL

  • ✅ WebSocket connections working correctly

  • ✅ Full functionality available from any device in tailnet

Environment: macOS + Docker + Tailscale Planka Version: 2.0.0-rc.3

Last updated