Why did Let's Encrypt renewal stop after upgrading Jitsi Docker?

Short answer

Docker releases stable-11146 and stable-11146-1 could issue certificates but could not renew them because Debian cron required root. [S1][S2][S3] PR #2305 replaced cron with a rootless acme.sh service in stable-11146-2; stable-11248 remains the latest release checked on 2026-10-05. [S2][S3][S4] Upgrade with matching Compose files, preserve the persistent certificate state, then check renewal and the certificate actually served over HTTPS. [S5][S6][S10]

Symptoms

On the affected Docker releases, the web container obtains its first certificate, then repeatedly fails to start cron. Eventually HTTPS presents an expired certificate. Issue #2301 records these lines: [S1][S2]

Text
web-1  | no crontab for s6
web-1  | seteuid: Operation not permitted

Read them from the project directory: [S13]

Terminal
docker compose logs --tail 200 web

Challenge routing can also fail after the scheduler is fixed. [S14]

Cause

The rootless web image runs as UID 1000. Debian cron needs privileges, exits on seteuid: Operation not permitted, and s6 starts it again. Initial issuance succeeds because startup calls acme.sh directly. [S2]

Stable-11146-2 replaces cron with the acme-renewal service and installs acme.sh with --nocron. The service waits 60 seconds, then runs acme.sh --cron --home /storage/acme.sh every 43,200 seconds, or 12 hours. It renews certificates when due. It disables itself if DISABLE_HTTPS=1, ENABLE_LETSENCRYPT is not 1, or the installed executable is missing. [S3][S6]

State locations in stable-11248 are: [S5][S7]

Purpose Container path Host path
Client, account and renewal metadata /storage/acme.sh ${CONFIG}/storage/web/acme.sh
Installed chain and key /storage/acme-certs/meet.example.com/fullchain.pem, /storage/acme-certs/meet.example.com/key.pem ${CONFIG}/storage/web/acme-certs/meet.example.com/

${CONFIG}/tmp/web-crontabs was the obsolete cron mount, removed by PR #2305. ${CONFIG}/tmp/web-load-test remains unrelated to certificate renewal. Startup copies old /config/acme.sh and /config/acme-certs into storage only when their new destinations do not exist. [S2][S5][S7]

Fix

Before changing either installation, run the first command in Verify and record the served certificate’s serial and notAfter. [S10]

  1. Docker, affected releases: upgrade the release and its Compose files. Use the stable-11248 release archive, preserve your .env, passwords and existing CONFIG directory, and apply the rootless directory migration if upgrading from an older release. Its writable storage must be accessible to UID 1000. [S2][S4][S5][S8]

    Set this in .env, then run the commands from the updated project directory. This recreates services, so schedule a maintenance window. Keep any additional Compose files you normally use in the invocation. [S5][S8][S13]

    Config
    JITSI_IMAGE_VERSION=stable-11248
    Terminal
    docker compose config --images
    docker compose pull
    docker compose up -d --force-recreate
    docker compose ps

    The core images must end with /web:stable-11248, /prosody:stable-11248, /jicofo:stable-11248 and /jvb:stable-11248. Custom Compose deployments need the matching storage mount and port mappings, not just a new web image. [S5]

  2. Docker stable-11248: check renewal prerequisites. For direct HTTPS termination in the web container, confirm these .env settings: [S7][S8]

    Config
    ENABLE_LETSENCRYPT=1
    DISABLE_HTTPS=0
    LETSENCRYPT_DOMAIN=meet.example.com
    LETSENCRYPT_EMAIL=admin@example.com
    HTTP_PORT=80
    HTTPS_PORT=443

    After changing these values, recreate web. Restarting alone does not apply environment changes. [S13]

    Terminal
    docker compose up -d --no-deps --force-recreate web

    Public TCP 80 must reach the standalone HTTP challenge. The corrected Compose mapping sends host port 80 to container port 8000, and PR #2300 adds --httpport 8000. TLS expiry does not prevent HTTP-01 validation. If an external proxy owns TLS, renew through its certificate manager. [S3][S5][S7][S8][S15]

  3. Docker stable-11248: run a due renewal check first. Use /command/with-contenv to load the service environment, including PATH for its Nginx hooks. Check due renewals and list records: [S6][S13]

    Terminal
    docker compose exec -T web /command/with-contenv /storage/acme.sh/acme.sh --cron --home /storage/acme.sh
    docker compose exec -T web /command/with-contenv /storage/acme.sh/acme.sh --list --home /storage/acme.sh
  4. Docker stable-11248: recover an expired certificate or force one controlled renewal. After fixing connectivity, select the record backing the served certificate. Use the ECC command below for an ec- key length. For an RSA certificate, omit --ecc. Renewal uses stored challenge settings and install paths. Its hooks stop and start Nginx, so use a maintenance window and avoid concurrent renewal attempts. [S7][S9]

    Terminal
    docker compose exec -T web /command/with-contenv /storage/acme.sh/acme.sh --renew -d meet.example.com --ecc --force --home /storage/acme.sh

    After successful renewal, restart web to ensure it loads the installed certificate, then run Verify. Preserve the ACME account and certificate directories. Repeated forced requests are not a substitute for fixing validation and can encounter CA rate limits. [S7][S9][S13][S15]

    Terminal
    docker compose restart web
  5. Debian/Ubuntu packages: identify the installed certificate client. Docker tags and its cron regression do not apply to the package helper. At jitsi-meet_11248, it uses acme.sh under /opt/acmesh/.acme.sh, installs /etc/jitsi/meet/meet.example.com.crt and the matching .key, and saves web-server and coturn reload commands. Run its due-renewal check: [S11]

    Terminal
    sudo /opt/acmesh/.acme.sh/acme.sh --cron --home /opt/acmesh/.acme.sh

    To recover that helper’s issuance and installation configuration, use: [S11]

    Terminal
    sudo /usr/share/jitsi-meet/scripts/install-letsencrypt-cert.sh admin@example.com meet.example.com

    This requests a certificate and configures reload hooks. Older installations actually managed by Certbot should instead test their saved renewal configuration, then renew due certificates: [S11][S12]

    Terminal
    sudo certbot renew --dry-run
    sudo certbot renew

Verify

For Docker stable-11248 and package installs, inspect the endpoint from a machine with OpenSSL 3.0: [S10]

Terminal
openssl s_client -connect meet.example.com:443 -servername meet.example.com </dev/null 2>/dev/null | openssl x509 -noout -issuer -dates -serial
openssl s_client -connect meet.example.com:443 -servername meet.example.com </dev/null 2>/dev/null | openssl x509 -noout -checkend 0
openssl s_client -connect meet.example.com:443 -servername meet.example.com -verify_hostname meet.example.com -verify_return_error </dev/null

The expiry check should print Certificate will not expire and exit zero. The trust and hostname check should show Verify return code: 0 (ok). Compare the new serial and notAfter with the pre-renewal certificate. These checks prove current validity; one manual renewal cannot prove future scheduling. [S10]

If it still fails

On Docker stable-11248, read web logs again. acme.sh is not installed in /storage/acme.sh, skipping renewals identifies a disabled renewal service. Check the writable storage mount and HTTPS settings. [S6][S7]

Issue #2321 reports a different failure on stable-11146-2: persisted metadata retained /var/run/s6/services/nginx instead of /run/service/nginx, plus the old validation port. The reporter rebuilt the saved ACME state and restored renewal. This is a community recovery report, not proof that PR #2305 failed. Preserve backups before rebuilding account state; the exact safe migration requires checking your stored certificate records. [S14]

For DNS, blocked TCP 80 or failed initial issuance, use Let’s Encrypt failed. Apply the version-specific package-client distinction above. [S16]

FAQ

Which release fixes rootless cron renewal?

Stable-11146-2 includes PR #2305. Stable-11248 is the latest release checked on 2026-10-05 and retains the service. [S3][S4][S6]

Should I run the web container as root?

Use the corrected rootless service. It calls acme.sh directly and no longer needs Debian cron. [S2][S6]

Is certificate state stored in CONFIG/tmp?

No. Current state is under ${CONFIG}/storage/web; the removed temporary mount held crontabs. [S2][S5]

Can I renew after the certificate has expired?

Yes, HTTP-01 uses port 80. Fix validation, renew with the matching client, and check the certificate served on port 443. [S9][S10][S15]

Sources

[S1] Issue #2301, 2026-08-08, community report, closed 2026-08-17.

[S2] PR #2305, 2026-08-17, maintainer explanation and source code.

[S3] stable-11146-2, 2026-08-17, release note, includes #2300 and #2305.

[S4] Latest release, checked 2026-10-05, release note, stable-11248 published 2026-09-14.

[S5] Compose, stable-11248, checked 2026-10-05, source code.

[S6] Renewal service, stable-11248 and s6 environment, v3.2.3.0, checked 2026-10-05, source code and official doc.

[S7] Web startup and TLS paths, checked 2026-10-05, source code.

[S8] Docker handbook, checked 2026-10-05, official doc.

[S9] acme.sh 3.0.7, Docker client pin and renewal instructions, checked 2026-10-05, source code and official doc.

[S10] OpenSSL x509, s_client, x509 output and TLS output, checked 2026-10-05, official docs and source code.

[S11] Package helper, jitsi-meet_11248, checked 2026-10-05, source code.

[S12] Certbot renewal, checked 2026-10-05, official doc.

[S13] Compose commands, exec, up and restart, checked 2026-10-05, official docs.

[S14] Issue #2321 and reporter’s follow-up, checked 2026-10-05, community report.

[S15] HTTP-01 and rate limits, checked 2026-10-05, official docs.

[S16] Let’s Encrypt failed, updated 2026-10-04, independent community guide.

Open questions

No running Jitsi server was used to test forced renewal, reload timing or expired-site recovery. Confirm those in a maintenance window. No universal migration for the stale metadata in #2321 was verified. Older package certificate-client transitions were not traced release by release.

Collect debug info

These commands gather what an engineer needs to diagnose a Jitsi server. Copy them, run them, and keep the output.

Terminal
# Run on the server, from your docker-jitsi-meet folder

# 1. Every service should be "running" (or "healthy")
docker compose ps

# 2. Which images and release tags are running
docker compose images

# 3. Recent logs from the core services
docker compose logs --tail=200 web prosody jicofo jvb

# 4. The settings most fixes depend on
grep -E '^(PUBLIC_URL|JVB_ADVERTISE_IPS|ENABLE_LETSENCRYPT|ENABLE_AUTH|AUTH_TYPE)=' .env

# 5. Is anything listening for media on UDP 10000?
sudo ss -ulnp | grep 10000

Installed with the Debian packages instead of Docker? Read the service logs with:

Terminal
sudo journalctl -u prosody -u jicofo -u jitsi-videobridge2 --since "1 hour ago"

Remove passwords, secrets and tokens before you share any output.

Send the output to an engineer

Stuck, or would rather not do this by hand?

Deploy it in one click

A private Jitsi server in your own AWS account with SSL, your domain and optional recording, transcription and JWT. Free 15 minute trial.

Start free trial

Talk to a Jitsi engineer

Setup, fixes, branding, recording, scaling. Tell us what is happening and we reply with a plan and a quote.

Get expert help

Related

Recently updated