{"record":{"id":"7332cb054a90d700","repo":"MHSanaei/3x-ui","slug":"postgres-unreachable-after-d-attempts-w","errorCode":null,"errorMessage":"postgres unreachable after %d attempts: %w","messagePattern":"postgres unreachable after (.+?) attempts: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"internal/database/db.go","lineNumber":2089,"sourceCode":"// used to be buried behind a generic startup failure.\nfunc openPostgresWithRetry(dsn string, c *gorm.Config) (*gorm.DB, error) {\n\tdelays := []time.Duration{0, 2 * time.Second, 5 * time.Second, 10 * time.Second, 20 * time.Second, 30 * time.Second}\n\tvar lastErr error\n\tfor i, delay := range delays {\n\t\tif delay > 0 {\n\t\t\ttime.Sleep(delay)\n\t\t}\n\t\tconn, err := gorm.Open(postgres.Open(dsn), c)\n\t\tif err == nil {\n\t\t\tif i > 0 {\n\t\t\t\tlog.Printf(\"postgres connection established on attempt %d/%d\", i+1, len(delays))\n\t\t\t}\n\t\t\treturn conn, nil\n\t\t}\n\t\tlastErr = err\n\t\tlog.Printf(\"postgres connection attempt %d/%d failed: %v\", i+1, len(delays), err)\n\t}\n\treturn nil, fmt.Errorf(\"postgres unreachable after %d attempts: %w\", len(delays), lastErr)\n}\n\nfunc sqliteJournalMode() string {\n\tswitch strings.ToUpper(strings.TrimSpace(os.Getenv(\"XUI_DB_JOURNAL_MODE\"))) {\n\tcase \"DELETE\":\n\t\treturn \"DELETE\"\n\tdefault:\n\t\treturn \"WAL\"\n\t}\n}\n\nfunc backupSQLiteStepPages() int {\n\tif sqliteJournalMode() == \"DELETE\" {\n\t\treturn 128\n\t}\n\treturn -1\n}\n","sourceCodeStart":2071,"sourceCodeEnd":2107,"githubUrl":"https://github.com/MHSanaei/3x-ui/blob/ad32144c42455696ea9f14e12168beac3e25f5d2/internal/database/db.go#L2071-L2107","documentation":"InitDB's Postgres path retries gorm.Open with escalating delays; after every attempt fails it wraps the last error in this aggregate. The underlying %w error (DNS failure, refused connection, auth error, bad DSN) is the real diagnosis — this message only says the retry budget ran out. It means the panel could not reach/authorize against PostgreSQL within the backoff window.","triggerScenarios":"XUI_DB_TYPE=postgres with an XUI_DB_DSN pointing at a not-yet-started container (race on first boot), wrong password/host, TLS mismatch, or firewall/SG blocking 5432.","commonSituations":"docker-compose panel starting before the postgres service healthcheck passes; DSN copied with wrong user/db name; postgres requiring sslmode=verify-full without the CA present; k8s NetworkPolicy blocking the panel pod.","solutions":["Read the wrapped lastErr in the log line just above — it names the actual failure (connect refused / auth / TLS).","Verify reachability and credentials: psql '<DSN>' -c 'select 1' from the panel host/container.","Fix startup ordering: add depends_on with condition: service_healthy for postgres, or an entrypoint wait loop.","For slow-starting remote DBs, ensure DSN has sane connect_timeout; the retry loop only covers initial open."],"exampleFix":"# before (compose race)\nservices:\n  x-ui:\n    environment: [XUI_DB_TYPE=postgres, XUI_DB_DSN=...]\n  # no ordering\n# after\nservices:\n  x-ui:\n    depends_on:\n      postgres:\n        condition: service_healthy","handlingStrategy":"retry","validationCode":"// Pre-flight the DSN before handing it to InitDB:\nsqlDB, err := sql.Open(\"pgx\", dsn)\nif err != nil { return err }\ndefer sqlDB.Close()\nctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\ndefer cancel()\nif err := sqlDB.PingContext(ctx); err != nil { return err }","typeGuard":null,"tryCatchPattern":"for attempt := 0; attempt < 5; attempt++ {\n    err := database.InitDB(path)\n    if err == nil || !strings.Contains(err.Error(), \"postgres unreachable\") {\n        break\n    }\n    time.Sleep(time.Duration(attempt+1) * 3 * time.Second)\n}","preventionTips":["In compose, gate the panel on the postgres healthcheck (condition: service_healthy).","Keep the DSN's connect_timeout small so failures surface fast; verify with psql before first launch.","Log and read the wrapped lastErr — it distinguishes auth vs network vs DNS."],"tags":["database","postgres","connection","startup","retry"],"backgroundTag":null,"analyzedSha":"ad32144c42455696ea9f14e12168beac3e25f5d2","analyzedAt":"2026-08-15T11:13:23.905Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}