diff --git a/backend/internal/api/routes/routes.go b/backend/internal/api/routes/routes.go index 1e9eb5ce..6830a539 100644 --- a/backend/internal/api/routes/routes.go +++ b/backend/internal/api/routes/routes.go @@ -38,6 +38,21 @@ func Register(router *gin.Engine, db *gorm.DB, cfg config.Config) error { return fmt.Errorf("auto migrate: %w", err) } + // Clean up invalid Let's Encrypt certificate associations + // Let's Encrypt certs are auto-managed by Caddy and should not be assigned via certificate_id + fmt.Println("Cleaning up invalid Let's Encrypt certificate associations...") + var hostsWithInvalidCerts []models.ProxyHost + if err := db.Joins("LEFT JOIN ssl_certificates ON proxy_hosts.certificate_id = ssl_certificates.id"). + Where("ssl_certificates.provider = ?", "letsencrypt"). + Find(&hostsWithInvalidCerts).Error; err == nil { + if len(hostsWithInvalidCerts) > 0 { + for _, host := range hostsWithInvalidCerts { + fmt.Printf("Removing invalid Let's Encrypt cert assignment from %s\n", host.DomainNames) + db.Model(&host).Update("certificate_id", nil) + } + } + } + router.GET("/api/v1/health", handlers.HealthHandler) api := router.Group("/api/v1") diff --git a/backend/internal/caddy/config.go b/backend/internal/caddy/config.go index f93f8d77..5df2ab95 100644 --- a/backend/internal/caddy/config.go +++ b/backend/internal/caddy/config.go @@ -93,17 +93,26 @@ func GenerateConfig(hosts []models.ProxyHost, storageDir string, acmeEmail strin } } - // Collect custom certificates + // Collect CUSTOM certificates only (not Let's Encrypt - those are managed by ACME) + // Only custom/uploaded certificates should be loaded via LoadPEM customCerts := make(map[uint]models.SSLCertificate) for _, host := range hosts { if host.CertificateID != nil && host.Certificate != nil { - customCerts[*host.CertificateID] = *host.Certificate + // Only include custom certificates, not ACME-managed ones + if host.Certificate.Provider == "custom" { + customCerts[*host.CertificateID] = *host.Certificate + } } } if len(customCerts) > 0 { var loadPEM []LoadPEMConfig for _, cert := range customCerts { + // Validate that custom cert has both certificate and key + if cert.Certificate == "" || cert.PrivateKey == "" { + fmt.Printf("Warning: Custom certificate %s missing certificate or key, skipping\n", cert.Name) + continue + } loadPEM = append(loadPEM, LoadPEMConfig{ Certificate: cert.Certificate, Key: cert.PrivateKey, @@ -111,11 +120,13 @@ func GenerateConfig(hosts []models.ProxyHost, storageDir string, acmeEmail strin }) } - if config.Apps.TLS == nil { - config.Apps.TLS = &TLSApp{} - } - config.Apps.TLS.Certificates = &CertificatesConfig{ - LoadPEM: loadPEM, + if len(loadPEM) > 0 { + if config.Apps.TLS == nil { + config.Apps.TLS = &TLSApp{} + } + config.Apps.TLS.Certificates = &CertificatesConfig{ + LoadPEM: loadPEM, + } } } diff --git a/frontend/src/components/ProxyHostForm.tsx b/frontend/src/components/ProxyHostForm.tsx index d7f82847..03a1da53 100644 --- a/frontend/src/components/ProxyHostForm.tsx +++ b/frontend/src/components/ProxyHostForm.tsx @@ -364,20 +364,23 @@ export default function ProxyHostForm({ host, onSubmit, onCancel }: ProxyHostFor {/* SSL Certificate Selection */}
+

+ Let's Encrypt certificates are managed automatically. Use custom certificates for self-signed or other providers. +

{/* SSL & Security Options */} diff --git a/frontend/src/pages/ProxyHosts.tsx b/frontend/src/pages/ProxyHosts.tsx index e6d93640..b4898bca 100644 --- a/frontend/src/pages/ProxyHosts.tsx +++ b/frontend/src/pages/ProxyHosts.tsx @@ -149,9 +149,14 @@ export default function ProxyHosts() { )} - {host.certificate && ( + {host.certificate && host.certificate.provider === 'custom' && (
- {host.certificate.name} ({host.certificate.provider}) + {host.certificate.name} (Custom) +
+ )} + {host.ssl_forced && !host.certificate && ( +
+ Let's Encrypt (Auto)
)}