From ba60a9afa51193f154574dd98832035817ca5e73 Mon Sep 17 00:00:00 2001 From: adman234 Date: Fri, 10 Jul 2026 10:33:24 -0500 Subject: [PATCH] Fix: SSL toggles (Force SSL, HTTP/2, HSTS) reset to off when requesting a new certificate on host creation When creating a Proxy Host, Redirection Host, or 404 Host and requesting a brand new Let's Encrypt certificate in the same submission, the ssl_forced/http2_support/hsts_enabled/hsts_subdomains fields the user enabled on the form were silently discarded and saved as false. Root cause: certificate_id is deleted from the payload before the initial insert (since "new" is not a real certificate id yet), so cleanSslHstsData() sees no certificate_id and zeroes the SSL toggles under its "no cert means SSL can't be forced" invariant - even though a certificate is about to be created moments later in the same request. The follow-up update() call that attaches the freshly created certificate_id only passed { id, certificate_id }, so the user's original toggle values were never re-applied. Fix: skip the premature cleanSslHstsData() zeroing during the initial insert when a certificate is being created, and forward the originally submitted ssl_forced/http2_support/hsts_enabled/hsts_subdomains values in the follow-up update() call, once the real certificate exists to validate them against. Co-Authored-By: Claude Sonnet 5 --- backend/internal/dead-host.js | 16 ++++++++++++++-- backend/internal/proxy-host.js | 18 ++++++++++++++++-- backend/internal/redirection-host.js | 18 ++++++++++++++++-- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/backend/internal/dead-host.js b/backend/internal/dead-host.js index ee27e53cc5..599ef2392c 100644 --- a/backend/internal/dead-host.js +++ b/backend/internal/dead-host.js @@ -46,7 +46,13 @@ const internalDeadHost = { // At this point the domains should have been checked data.owner_user_id = access.token.getUserId(1); - const thisData = internalHost.cleanSslHstsData(data); + + // Don't clean the ssl_forced/http2_support/hsts_* fields yet if a + // certificate is about to be requested. certificate_id has already + // been stripped above, so cleanSslHstsData would see "no cert" and + // zero out the SSL toggles the user just submitted, even though a + // certificate is created moments later in this same request. + const thisData = createCertificate ? data : internalHost.cleanSslHstsData(data); // Fix for db field not having a default value // for this optional field. @@ -69,10 +75,16 @@ const internalDeadHost = { if (createCertificate) { const cert = await internalCertificate.createQuickCertificate(access, data); - // update host with cert id + // Update host with cert id and re-apply the originally submitted + // SSL toggles, now that a real certificate exists for them to + // validate against. await internalDeadHost.update(access, { id: row.id, certificate_id: cert.id, + ssl_forced: thisData.ssl_forced, + http2_support: thisData.http2_support, + hsts_enabled: thisData.hsts_enabled, + hsts_subdomains: thisData.hsts_subdomains, }); } diff --git a/backend/internal/proxy-host.js b/backend/internal/proxy-host.js index 2c159d48ad..3275dfd6ab 100644 --- a/backend/internal/proxy-host.js +++ b/backend/internal/proxy-host.js @@ -49,7 +49,15 @@ const internalProxyHost = { .then(() => { // At this point the domains should have been checked thisData.owner_user_id = access.token.getUserId(1); - thisData = internalHost.cleanSslHstsData(thisData); + + // Don't clean the ssl_forced/http2_support/hsts_* fields yet if a + // certificate is about to be requested. certificate_id has already + // been stripped above, so cleanSslHstsData would see "no cert" and + // zero out the SSL toggles the user just submitted, even though a + // certificate is created moments later in this same request. + if (!createCertificate) { + thisData = internalHost.cleanSslHstsData(thisData); + } // Fix for db field not having a default value // for this optional field. @@ -64,10 +72,16 @@ const internalProxyHost = { return internalCertificate .createQuickCertificate(access, thisData) .then((cert) => { - // update host with cert id + // Update host with cert id and re-apply the originally submitted + // SSL toggles, now that a real certificate exists for them to + // validate against. return internalProxyHost.update(access, { id: row.id, certificate_id: cert.id, + ssl_forced: thisData.ssl_forced, + http2_support: thisData.http2_support, + hsts_enabled: thisData.hsts_enabled, + hsts_subdomains: thisData.hsts_subdomains, }); }) .then(() => { diff --git a/backend/internal/redirection-host.js b/backend/internal/redirection-host.js index 542439fd36..715b61f8c8 100644 --- a/backend/internal/redirection-host.js +++ b/backend/internal/redirection-host.js @@ -49,7 +49,15 @@ const internalRedirectionHost = { .then(() => { // At this point the domains should have been checked thisData.owner_user_id = access.token.getUserId(1); - thisData = internalHost.cleanSslHstsData(thisData); + + // Don't clean the ssl_forced/http2_support/hsts_* fields yet if a + // certificate is about to be requested. certificate_id has already + // been stripped above, so cleanSslHstsData would see "no cert" and + // zero out the SSL toggles the user just submitted, even though a + // certificate is created moments later in this same request. + if (!createCertificate) { + thisData = internalHost.cleanSslHstsData(thisData); + } // Fix for db field not having a default value // for this optional field. @@ -64,10 +72,16 @@ const internalRedirectionHost = { return internalCertificate .createQuickCertificate(access, thisData) .then((cert) => { - // update host with cert id + // Update host with cert id and re-apply the originally submitted + // SSL toggles, now that a real certificate exists for them to + // validate against. return internalRedirectionHost.update(access, { id: row.id, certificate_id: cert.id, + ssl_forced: thisData.ssl_forced, + http2_support: thisData.http2_support, + hsts_enabled: thisData.hsts_enabled, + hsts_subdomains: thisData.hsts_subdomains, }); }) .then(() => {