nusaas@self-host:~$ [back-to-home]
[docker-hub]
> diagnose --verbose status.log

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-01

CORS Policy / Preflight Request Blocked

[Severity: High]

// 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

In standard bash, $# is a special variable representing the number of script arguments. In older installers, the .env generator heredoc was unescaped, causing bash to evaluate $# to 0. This wrote CORS_ALLOWED_ORIGIN_PATTERNS=#^https?://.*0 to your config, breaking regular expression matching.

// IMMEDIATE_HOT_FIX

Run this on your server host to repair the config regex and recreate the API container with updated variables:

sed -i 's|CORS_ALLOWED_ORIGIN_PATTERNS=#^https?://.*0|CORS_ALLOWED_ORIGIN_PATTERNS=#^https?://.*\$#|g' ~/docker/nusaas/.env
docker compose up -d
CACHE-02

Browser Connects to Stale/Old Domains

[Severity: Medium]

// SYMPTOM

You updated the domain settings in .env.frontend and recreated containers, but browser developer console still shows requests targeting old domains (e.g. wss://api.olddomain.com).

// 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

  1. Force recreate the containers to inject the new env values:
    docker compose up -d
  2. Test in an Incognito / Private Window (bypasses Service Worker cache).
  3. 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).
  4. If using Firefox, unregister the service worker directly at: about:debugging#/runtime/this-firefox
SSL-03

Cloudflare ERR_SSL_VERSION_OR_CIPHER_MISMATCH

[Severity: High]

// 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.com or app-erp.yourdomain.com
  • Use api.yourdomain.com or api-erp.yourdomain.com
DB-04

Database Migration & Seeding Hangs

[Severity: Medium]

// 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

Write a MariaDB client configuration directly inside the running backend container to skip SSL validation, then manually run the migration command:

docker compose exec api sh -c "printf '[client]\nskip-ssl\n' > /etc/my.cnf"
docker compose exec -T api php artisan migrate --seed --force
DRV-05

Relational Driver Error (PostgreSQL)

[Severity: High]

// SYMPTOM

could not find driver (Connection: pgsql, Host: nusaas-db, Port: 5432, Database: nusaas ...)

// ROOT_CAUSE

Older releases of the FrankenPHP backend base image (ghostdev1/nusaas-backend) were compiled without the native PostgreSQL client binaries and PHP extension (pdo_pgsql).

// RESOLUTION

Pull down the latest v1.0.4+ images which include compiled PostgreSQL extensions, and recreate the stack:

docker compose pull
docker compose up -d
DOCKER-06

Worker Crash Loops & Unhealthy Statuses

[Severity: Medium]

// SYMPTOM

nusaas-worker Restarting (2) 59 seconds ago
nusaas-ws Up 30 minutes (unhealthy)
nusaas-cron Up 30 minutes (unhealthy)

// ROOT_CAUSE

1. The worker container command runs supervisord on a config located at /etc/supervisor/conf.d/supervisord.conf which does not exist (the correct path inside the container is /var/www/docker/supervisor/supervisord.conf).
2. The cron and ws containers inherit the image-level healthcheck on port 4000. Since they do not run a web server, their health checks fail even though they are running properly.

// REMEDY

Modify the service configurations in your docker-compose.yml:

  1. Update the worker service command:
    command: /usr/bin/supervisord -n -c /var/www/docker/supervisor/supervisord.conf
  2. Add healthcheck: disable: true to the worker, cron, and ws services.
  3. Recreate the containers:
    docker compose up -d
DIAG-07

NuSaaS Diagnostics Suite

[Info & Utilities]

// 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/PostgreSQL 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