MHSanaei/3x-ui · critical
postgres unreachable after %d attempts: %w
Error message
postgres unreachable after %d attempts: %w
What it means
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.
Source
Thrown at internal/database/db.go:2089
// used to be buried behind a generic startup failure.
func openPostgresWithRetry(dsn string, c *gorm.Config) (*gorm.DB, error) {
delays := []time.Duration{0, 2 * time.Second, 5 * time.Second, 10 * time.Second, 20 * time.Second, 30 * time.Second}
var lastErr error
for i, delay := range delays {
if delay > 0 {
time.Sleep(delay)
}
conn, err := gorm.Open(postgres.Open(dsn), c)
if err == nil {
if i > 0 {
log.Printf("postgres connection established on attempt %d/%d", i+1, len(delays))
}
return conn, nil
}
lastErr = err
log.Printf("postgres connection attempt %d/%d failed: %v", i+1, len(delays), err)
}
return nil, fmt.Errorf("postgres unreachable after %d attempts: %w", len(delays), lastErr)
}
func sqliteJournalMode() string {
switch strings.ToUpper(strings.TrimSpace(os.Getenv("XUI_DB_JOURNAL_MODE"))) {
case "DELETE":
return "DELETE"
default:
return "WAL"
}
}
func backupSQLiteStepPages() int {
if sqliteJournalMode() == "DELETE" {
return 128
}
return -1
}
View on GitHub (pinned to ad32144c42)
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.
Example fix
# before (compose race)
services:
x-ui:
environment: [XUI_DB_TYPE=postgres, XUI_DB_DSN=...]
# no ordering
# after
services:
x-ui:
depends_on:
postgres:
condition: service_healthy Defensive patterns
Strategy: retry
Validate before calling
// Pre-flight the DSN before handing it to InitDB:
sqlDB, err := sql.Open("pgx", dsn)
if err != nil { return err }
defer sqlDB.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := sqlDB.PingContext(ctx); err != nil { return err } Try / catch
for attempt := 0; attempt < 5; attempt++ {
err := database.InitDB(path)
if err == nil || !strings.Contains(err.Error(), "postgres unreachable") {
break
}
time.Sleep(time.Duration(attempt+1) * 3 * time.Second)
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- XUI_DB_TYPE=postgres but XUI_DB_DSN is empty
- open postgres destination: %w
- open postgres source: %w
- sqlite backup is unavailable for PostgreSQL
- destination DSN is required
AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15).
Data as JSON: /api/errors/7332cb054a90d700.
Report an issue: GitHub.