Self-Host Troubleshooting Guide
A comprehensive manual mapping root causes, immediate hot fixes, and permanent remedies for common infrastructure issues encountered during self-hosting.
CORS Policy / Preflight Request Blocked
// SYMPTOM
Access to fetch at 'https://api.yourdomain.com/api/auth/login' from origin 'https://app.yourdomain.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present.
// ROOT_CAUSE
The frontend origin does not match CORS_ALLOWED_ORIGIN_PATTERNS, or the regex begins with an unquoted #. Dotenv treats an unquoted leading hash as a comment, leaving Laravel with no allowed browser origin.
// IMMEDIATE_HOT_FIX
Set a quoted exact regex for your frontend origin, then recreate every backend runtime so it loads the changed environment:
# In /opt/nusaas/.env: CORS_ALLOWED_ORIGIN_PATTERNS='#^https://app\.yourdomain\.com$#' docker compose up -d --force-recreate api worker cron ws
Browser Connects to Stale/Old Domains
// SYMPTOM
You updated domain settings in web/.env.frontend, but the browser still targets old API or WebSocket domains.
// ROOT_CAUSE
1. Running docker compose restart only restarts container processes; it does not recreate them with new environment variables.
2. The React Frontend uses a Service Worker (sw.js) that aggressively caches JS files locally in the client browser, forcing it to keep loading the old config.
// SOLUTION_STEPS
- Force recreate the containers to inject the new env values:
docker compose up -d --force-recreate web
- Test in an Incognito / Private Window (bypasses Service Worker cache).
- In your main window, open DevTools (F12), navigate to the Network tab, check Disable Cache, and reload with Ctrl+F5 (Windows) or Cmd+Shift+R (Mac).
- If using Firefox, unregister the service worker directly at:
about:debugging#/runtime/this-firefox
Cloudflare ERR_SSL_VERSION_OR_CIPHER_MISMATCH
// SYMPTOM
This site can’t provide a secure connection. app.erp.yourdomain.com uses an unsupported protocol.
ERR_SSL_VERSION_OR_CIPHER_MISMATCH
// ROOT_CAUSE
Cloudflare's free Universal SSL certificate only covers wildcards up to the 3rd level (*.yourdomain.com). If you deploy using nested 4th-level subdomains (like app.erp.yourdomain.com), Cloudflare cannot negotiate a secure SSL handshake at the Edge unless you purchase their Advanced Certificate Manager (ACM).
// REMEDY
Flatten your subdomain structure to the 3rd level. Remove the nested erp. segment entirely:
- Use
app.yourdomain.comorapp-erp.yourdomain.com - Use
api.yourdomain.comorapi-erp.yourdomain.com
Database Migration & Seeding Hangs
// SYMPTOM
The installation script gets stuck indefinitely at:
[NuSaaS] Running database migrations and seeding... [/]
// ROOT_CAUSE
When running migrations, Laravel shells out to the native mysql binary inside the backend container to load the schema dump file. If your backend container's default MySQL client (Debian's MariaDB client) connects to a MySQL 8.0 server advertising self-signed SSL certs, the handshake fails/hangs with ERROR 2026 (HY000): TLS/SSL error.
// IMMEDIATE_HOT_FIX
Current backend images configure the container client correctly. Pull and recreate the API before retrying migrations:
docker compose pull api docker compose up -d --force-recreate api docker compose exec -T api php artisan migrate --seed --force
Automatic HTTPS Certificate Fails
// SYMPTOM
The proxy runs, but the app or API hostname has no valid certificate.
// ROOT_CAUSE
DNS does not point to this host, a cloud or host firewall blocks 80/443, or another service owns those ports. On WSL2, the conflicting listener may be a Windows process.
// DIAGNOSIS
docker compose ps docker compose logs --since=10m proxy ss -ltn '( sport = :80 or sport = :443 )' # Replace with your public API hostname: curl -I https://api.example.com/api/health
NuSaaS Diagnostics Suite
// 1. CHECKING_CONTAINER_HEALTH
Query container statuses, check if their internal health checks are passing, and ping the API route directly from the server:
# Get docker containers status & health indicators docker compose ps # Check the API health endpoint directly inside the API container docker compose exec api curl -sf http://localhost:4000/api/health # Verify actual HTTP response headers from the API server docker compose exec api curl -i http://localhost:4000/api/health
// 2. LIVE_LOG_STREAMING
Stream logs in real-time to intercept errors during database connection failures, mail queue issues, or application crashes:
# Stream logs from all running containers docker compose logs -f # Stream only backend API container logs docker compose logs -f api # Stream queue worker logs docker compose logs -f worker # Stream MySQL logs docker compose logs -f db
// 3. CONTAINER_SHELLS_AND_DATABASE_DIAGNOSTICS
Access container environments directly or trigger internal database pingers to verify connection integrity:
# Open an interactive shell inside the API container docker compose exec -it api bash # Monitor DB connection stats in Laravel docker compose exec api php artisan db:monitor # Directly test DB connection PDO instantiation docker compose exec api php artisan tinker --execute="DB::connection()->getPdo();" # Get current system stats (RAM/CPU usage per container) docker stats --no-stream