Conversation
…operation init_le() now throws when the lock is held, so site create (wp/php/html) and `ee site update --ssl=le` reach their existing catch/rollback paths instead of exiting after the site root, containers and WordPress are already set up but before the site DB entry exists. update_alias_domains() now takes the lock before it dumps the compose file with HTTPS disabled for the HTTP-01 challenge, so a busy lock can no longer leave an LE site serving without HTTPS and with the new alias only in the compose file. A failed fopen() of the lock file now reports that instead of claiming another SSL operation is running.
Failing fast made a `--ssl=le` create that overlapped the nightly renewal install the whole site and then roll it back, and made the nightly `ssl-renew --all` skip every site when a manual SSL command was running. Poll the non-blocking flock for up to 120 seconds (600 for `ssl-renew`, which cron runs) with a waiting message, then fail with the same error or roll back as before.
EE_Site_Command registers SIGINT/SIGTERM handlers, but class-ee-site.php has no declare(ticks), so during the wait loop a Ctrl-C stayed pending and sleep() just resumed: the command kept waiting the full 120 s (600 s for ssl-renew). Dispatch pending signals after each poll so the site type's rollback handler runs and the command exits.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Nothing serialized SSL operations. A cron
ssl-renew --allrunning concurrently with a manualee site ssl-verify/ssl-renew/--ssl=lecreate or update (or two crons) races on the shared HTTP-01 state: the challenge files undernginx-proxy/html/.well-known/acme-challengeandvhost.d/default, whichssl_verify()wipes wholesale viacleanup(), plus overlapping nginx-proxy reloads. Operations on the same site also race on itsacme-conf/var/<domain>state andcertificate_order.json. No flock existed anywhere on the SSL path.Fix
Acquire a single global, exclusive
flock(EE_ROOT_DIR/ssl-global.lock) at the three ACME entry points —init_le()(beforeregister()/authorize()write account/order state),ssl_verify(), andssl_renew(). If another process holds it, the command printsWaiting up to 120s for another SSL operation to finish...and polls the non-blocking lock once a second for up to 120 seconds (600 seconds forssl-renew, which cron runs), then fails with "Another SSL operation is already in progress on this server. Wait for it to finish and retry." Pending SIGINT/SIGTERM signals are dispatched after each poll (pcntl_signal_dispatch()), so Ctrl-C or a SIGTERM ends the wait: the site type's rollback handler runs and the command exits (duringcreatethat removes the partial site, as Ctrl-C does elsewhere in create). The lock is held for the whole operation and released automatically on process exit (advisory flock — crash-safe).Where the lock still can't be obtained mid-operation once the wait runs out, state is rolled back rather than left half-done: in
init_le()the busy lock throws, soee site create --ssl=legoes through the normal clean-up andee site update --ssl=ledoesn't save the SSL flag; for an alias change on an LE site the lock is taken before the compose file is rewritten, so the site keeps HTTPS and the DB is unchanged. If the lock file can't be opened, the error says so instead of claiming another operation is running.The handle is a process-level
staticwith a reentrancy short-circuit, and this is load-bearing:ssl-renew --alldispatches each site viaEE::run_commandin one process (a fresh command instance per site), andflockdenies a second lock on the same file via a different fd even within the same process — so an instance-level guard would make site #2 of--allwrongly error. The static handle means the first acquire locks and every later/nested acquire (init_le → ssl_verify; each--allsite) returns reentrantly. (It differs from the backup lock's instance handle and explicit release: SSL's--all-in-one-process pattern needs a process-wide handle and no per-site release.)Testing
Manual: hold the lock from another shell (
flock /opt/easyengine/ssl-global.lock sleep 150), thenee site ssl-verify <site>prints the waiting message and exits after 120 seconds with "Another SSL operation is already in progress…";ee site ssl-renew <site>waits and completes once the lock is released;ee site create <x> --ssl=lethat can't get the lock within 120 seconds cleans up completely; Ctrl-C or SIGTERM during the wait rolls back and exits immediately;ee site update <le-site> --add-alias-domains=…that can't get the lock changes nothing. Without contention, an LE create (nestedinit_le→ssl_verify) andssl-renew --allacross several LE sites work normally.Tested on Ubuntu 26.04 with EasyEngine 4.12.0 with a real Let's Encrypt certificate (uncontended create, and
ssl-renew --allacross two sites flagged as Let's Encrypt) and lock contention on renew, verify, alias change, HTML and WordPresscreate --ssl=le, andupdate --ssl=le. The bounded wait was tested with the lock held from another shell:ssl-verifyfailed at 120 seconds,ssl-renewfinished as soon as the lock was released (after about 30 seconds), an HTMLcreate --ssl=lerolled back cleanly at the 120-second limit, and Ctrl-C or SIGTERM during the wait (including duringcreate) rolled back and exited right away, leaving nothing behind.