Skip to content

fix(ssl): copy and validate custom certs on site update - #489

Merged
mrrobot47 merged 5 commits into
EasyEngine:developfrom
mrrobot47:fix/update-ssl-custom-copy-certs
Sep 28, 2026
Merged

mrrobot47 merged 5 commits into
EasyEngine:developfrom
mrrobot47:fix/update-ssl-custom-copy-certs

Conversation

@mrrobot47

@mrrobot47 mrrobot47 commented Jun 30, 2026 •

Copy link
Copy Markdown
Member

Problem

ee site update --ssl=custom set the DB flag and enabled HTTPS but never copied the certificate files. update_ssl() calls www_ssl_wrapper(), which explicitly skips all cert work for custom, and the cert-copy helpers (validate_site_custom_ssl() / custom_site_ssl()) were only invoked from the create path. Result: HTTPS turned on with no certs in nginx-proxy/certs/, so the site served a wrong/default cert. This is also the path Cloner.php recommends after cloning a custom-SSL site, so that guidance led to a broken state too.

Fix

In update_ssl(), when the target type is custom, mirror the create path: validate --ssl-key/--ssl-crt via validate_site_custom_ssl() and copy them via custom_site_ssl() before www_ssl_wrapper(). Also documents the --ssl-key/--ssl-crt flags and adds an example. To replace an expiring custom cert, disable SSL and re-enable it with the new pair (ee site update <site> --ssl=off, then ee site update <site> --ssl=custom --ssl-key=... --ssl-crt=...); running --ssl=custom on a site that already has SSL still stops with "SSL is already enabled".

Missing or nonexistent --ssl-key/--ssl-crt fails fast (inside the existing try/catch, before $site->save()), and so does a certificate that isn't valid PEM, a broken chain or a key that doesn't match the certificate, through #492's assert_valid_cert_key_pair() in validate_site_custom_ssl(). Nothing is copied and there is no partial DB-vs-cert state. Re-enabling with the files already in nginx-proxy/certs/ as the source no longer truncates them.

Known limitation

If the post-copy bring-up (www_ssl_wrapper) throws, the copied cert files are left in place (no rollback); re-running the command overwrites them. The create path's catch_clean unwind is not mirrored here — a follow-up could unify the two cert paths.

Testing

Manual: create a non-SSL site, generate a self-signed key/crt, run ee site update <site> --ssl=custom --ssl-key=... --ssl-crt=..., and assert nginx-proxy/certs/<site>.key|.crt exist and the served cert matches.

Tested on Ubuntu 26.04 with EasyEngine 4.12.0: valid pair with relative paths (served cert matches, HTTP redirects to HTTPS), missing flags, nonexistent path and mismatched pair (rejected, nothing copied), same-path re-enable, the off-then-custom replacement flow, and create --ssl=custom regressions.

Merge note

Depends on #492 and must merge after it. The certificate content checks on site update (PEM parse, chain, key match) come from #492's validator, so this PR no longer carries its own key/cert match check.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

… key

Only the existence of --ssl-key/--ssl-crt was checked, so a mismatched pair or a non-PEM file was copied into nginx-proxy/certs and the command reported success. nginx-proxy then fails `nginx -t` for the whole config, which blocks proxy reloads for every site and would stop the proxy from starting after a restart. Validate the pair with openssl_x509_check_private_key() before anything is copied.
Symfony Filesystem::copy() opens the target for writing before reading the source, so passing the files already in nginx-proxy/certs (the natural way to re-enable custom SSL after --ssl=off) left an empty key and cert while the command reported success. Skip the copy when the source already is the destination.
assert_valid_cert_key_pair() from EasyEngine#492 runs in validate_site_custom_ssl() before this check and already rejects an unparseable or mismatched pair, so this second openssl_x509_check_private_key() call can never fail. Merge EasyEngine#492 before this PR.
@mrrobot47
mrrobot47 merged commit 22f9733 into EasyEngine:develop Sep 28, 2026
1 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants