From be7e92af64f2e5de5e170a321f5947bf79105ea4 Mon Sep 17 00:00:00 2001 From: Riddhesh Sanghvi Date: Tue, 30 Jun 2026 15:49:28 +0530 Subject: [PATCH 1/5] fix(ssl): copy and validate custom certs on site update --- src/helper/class-ee-site.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/helper/class-ee-site.php b/src/helper/class-ee-site.php index 08ad268e..ae3bfd2f 100644 --- a/src/helper/class-ee-site.php +++ b/src/helper/class-ee-site.php @@ -420,6 +420,12 @@ protected function delete_site( $level, $site_url, $site_fs_path, $db_data = [] * [--wildcard] * : Enable wildcard SSL on site. * + * [--ssl-key=] + * : Path to the SSL key file. Required with --ssl=custom. + * + * [--ssl-crt=] + * : Path to the SSL crt file. Required with --ssl=custom. + * * [--php=] * : PHP version for site. Currently only supports PHP 5.6, 7.0, 7.2, 7.3, 7.4, 8.0, 8.1, 8.2, 8.3, 8.4, and 8.5. * --- @@ -471,6 +477,9 @@ protected function delete_site( $level, $site_url, $site_fs_path, $db_data = [] * # Add self-signed SSL to non-ssl site * $ ee site update example.com --ssl=self * + * # Add custom SSL to non-ssl site + * $ ee site update example.com --ssl=custom --ssl-key=/path/to/site.key --ssl-crt=/path/to/site.crt + * * # Update PHP version of site. * $ ee site update example.com --php=8.0 * @@ -937,6 +946,13 @@ protected function update_ssl( $assoc_args ) { $this->site_data['site_ssl'] = $ssl; if ( $ssl ) { + // www_ssl_wrapper() skips cert work for custom SSL, so mirror the create + // path here: validate the provided key/crt and copy them into the + // nginx-proxy certs dir before enabling HTTPS, else the site serves a wrong cert. + if ( 'custom' === $ssl ) { + $this->validate_site_custom_ssl( get_flag_value( $assoc_args, 'ssl-key' ), get_flag_value( $assoc_args, 'ssl-crt' ) ); + $this->custom_site_ssl(); + } $this->www_ssl_wrapper( [ 'nginx' ] ); } else { $this->disable_ssl(); From a1164bba036e7eef5a7d1641bb77b7ad65df319c Mon Sep 17 00:00:00 2001 From: Riddhesh Sanghvi Date: Thu, 24 Sep 2026 10:41:21 +0000 Subject: [PATCH 2/5] fix(ssl): reject custom cert that is unreadable or does not match its 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. --- src/helper/class-ee-site.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/helper/class-ee-site.php b/src/helper/class-ee-site.php index ae3bfd2f..ef75640c 100644 --- a/src/helper/class-ee-site.php +++ b/src/helper/class-ee-site.php @@ -2271,6 +2271,11 @@ protected function validate_site_custom_ssl( $ssl_key, $ssl_crt ) { } else { throw new \Exception( 'ssl-key OR ssl-crt path does not exist' ); } + + // nginx-proxy fails its config test on an unreadable or mismatched pair, which blocks reloads for every site. + if ( ! openssl_x509_check_private_key( file_get_contents( $this->site_data['ssl_crt'] ), file_get_contents( $this->site_data['ssl_key'] ) ) ) { + throw new \Exception( 'ssl-crt is not a valid PEM certificate or does not match ssl-key' ); + } } /** From 09813d9f6341a94692d9f6decbbdd106d10ba9e1 Mon Sep 17 00:00:00 2001 From: Riddhesh Sanghvi Date: Thu, 24 Sep 2026 10:41:21 +0000 Subject: [PATCH 3/5] fix(ssl): don't truncate custom cert when copied onto itself 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. --- src/helper/class-ee-site.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/helper/class-ee-site.php b/src/helper/class-ee-site.php index ef75640c..f8250656 100644 --- a/src/helper/class-ee-site.php +++ b/src/helper/class-ee-site.php @@ -2286,8 +2286,13 @@ protected function custom_site_ssl() { $ssl_key_dest = sprintf( '%1$s/nginx-proxy/certs/%2$s.key', remove_trailing_slash( EE_SERVICE_DIR ), $this->site_data['site_url'] ); $ssl_crt_dest = sprintf( '%1$s/nginx-proxy/certs/%2$s.crt', remove_trailing_slash( EE_SERVICE_DIR ), $this->site_data['site_url'] ); - $this->fs->copy( $this->site_data['ssl_key'], $ssl_key_dest, true ); - $this->fs->copy( $this->site_data['ssl_crt'], $ssl_crt_dest, true ); + // Copying a file onto itself truncates it, e.g. when re-enabling SSL with the files already in the certs dir. + if ( realpath( $ssl_key_dest ) !== $this->site_data['ssl_key'] ) { + $this->fs->copy( $this->site_data['ssl_key'], $ssl_key_dest, true ); + } + if ( realpath( $ssl_crt_dest ) !== $this->site_data['ssl_crt'] ) { + $this->fs->copy( $this->site_data['ssl_crt'], $ssl_crt_dest, true ); + } } /** From 3197e6c9b247cdf68c0242cf181b2a623765f070 Mon Sep 17 00:00:00 2001 From: Riddhesh Sanghvi Date: Mon, 28 Sep 2026 10:45:17 +0000 Subject: [PATCH 4/5] refactor(ssl): drop the key/cert match check covered by #492 assert_valid_cert_key_pair() from #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 #492 before this PR. --- src/helper/class-ee-site.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/helper/class-ee-site.php b/src/helper/class-ee-site.php index f8250656..94d93d36 100644 --- a/src/helper/class-ee-site.php +++ b/src/helper/class-ee-site.php @@ -2271,11 +2271,6 @@ protected function validate_site_custom_ssl( $ssl_key, $ssl_crt ) { } else { throw new \Exception( 'ssl-key OR ssl-crt path does not exist' ); } - - // nginx-proxy fails its config test on an unreadable or mismatched pair, which blocks reloads for every site. - if ( ! openssl_x509_check_private_key( file_get_contents( $this->site_data['ssl_crt'] ), file_get_contents( $this->site_data['ssl_key'] ) ) ) { - throw new \Exception( 'ssl-crt is not a valid PEM certificate or does not match ssl-key' ); - } } /** From 96e5efdacbfa0b772fa6934e21a16275283c170c Mon Sep 17 00:00:00 2001 From: Riddhesh Sanghvi Date: Mon, 28 Sep 2026 10:45:17 +0000 Subject: [PATCH 5/5] style(ssl): shorten the custom cert comment in update_ssl() --- src/helper/class-ee-site.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/helper/class-ee-site.php b/src/helper/class-ee-site.php index 94d93d36..b7b64ddc 100644 --- a/src/helper/class-ee-site.php +++ b/src/helper/class-ee-site.php @@ -946,9 +946,7 @@ protected function update_ssl( $assoc_args ) { $this->site_data['site_ssl'] = $ssl; if ( $ssl ) { - // www_ssl_wrapper() skips cert work for custom SSL, so mirror the create - // path here: validate the provided key/crt and copy them into the - // nginx-proxy certs dir before enabling HTTPS, else the site serves a wrong cert. + // www_ssl_wrapper() skips cert work for custom SSL, so validate and copy the provided pair first. if ( 'custom' === $ssl ) { $this->validate_site_custom_ssl( get_flag_value( $assoc_args, 'ssl-key' ), get_flag_value( $assoc_args, 'ssl-crt' ) ); $this->custom_site_ssl();