Public Nextcloud traffic should reach the login page, WebDAV endpoints, sharing links, and mobile clients over browser-trusted HTTPS. An Nginx virtual host with an ACME certificate removes certificate warnings and lets Nextcloud generate secure URLs for background jobs, notifications, and file links.
The HTTPS change assumes an existing Nextcloud installation already works through Nginx on plain HTTP and that the public DNS name points to the server. certbot uses the Nginx plugin to request the certificate, install the certificate paths, and leave renewal automation in place.
Keep the Nextcloud server block aligned with the current official Nginx example, especially the /.well-known exceptions and the PHP handoff. If a separate reverse proxy or load balancer terminates TLS before requests reach this host, configure Nextcloud proxy trust instead of treating the application server as the public TLS endpoint.
$ getent ahosts cloud.example.com 203.0.113.20 STREAM cloud.example.com 203.0.113.20 DGRAM 203.0.113.20 RAW
If the hostname also has an .AAAA record, that IPv6 address must reach the same Nginx host or Let's Encrypt validation can fail from validators that choose IPv6.
$ curl -I http://cloud.example.com/status.php HTTP/1.1 200 OK Server: nginx Content-Type: application/json; charset=utf-8 ##### snipped #####
certbot with --nginx normally uses the HTTP-01 validation path, so the hostname must be reachable from the public Internet on port 80 unless DNS validation is used instead.
$ sudoedit /etc/nginx/sites-available/cloud.example.com
Use the file already linked from /etc/nginx/sites-enabled. A new file is only needed when the existing Nextcloud server block is still the default site.
server {
listen 80;
listen [::]:80;
server_name cloud.example.com;
root /var/www/nextcloud;
location ^~ /.well-known {
location = /.well-known/carddav { return 301 /remote.php/dav/; }
location = /.well-known/caldav { return 301 /remote.php/dav/; }
location /.well-known/acme-challenge { try_files $uri $uri/ =404; }
return 301 /index.php$request_uri;
}
location / {
try_files $uri $uri/ /index.php$request_uri;
}
}
The full Nextcloud server block also needs the deny rules, PHP-FPM location, static asset handling, and security headers from the current official Nextcloud Nginx example. Do not replace a complete working site with only this excerpt.
$ sudo nginx -t nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful
A syntax error here must be fixed before running certbot, because the Nginx plugin needs to parse and reload the loaded configuration.
Related: How to test Nginx configuration
$ sudo systemctl reload nginx
Related: How to manage the Nginx service
$ sudo apt install --assume-yes certbot python3-certbot-nginx The following NEW packages will be installed: certbot python3-certbot-nginx ##### snipped #####
Use one certbot packaging path consistently. Upstream certbot instructions for Ubuntu commonly use snap packages, while distro packages use package-manager service and timer names.
$ sudo certbot --nginx -d cloud.example.com Saving debug log to /var/log/letsencrypt/letsencrypt.log Requesting a certificate for cloud.example.com Successfully received certificate. Certificate is saved at: /etc/letsencrypt/live/cloud.example.com/fullchain.pem Key is saved at: /etc/letsencrypt/live/cloud.example.com/privkey.pem Deploying certificate Successfully deployed certificate for cloud.example.com to /etc/nginx/sites-enabled/cloud.example.com
Add each public alias with another -d option only when that name resolves to this Nginx server and should be covered by the same certificate. Wildcard names require DNS validation instead of the Nginx HTTP-01 flow.
Related: How to configure Let's Encrypt SSL in Nginx
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name cloud.example.com;
root /var/www/nextcloud;
index index.php index.html /index.php$request_uri;
ssl_certificate /etc/letsencrypt/live/cloud.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/cloud.example.com/privkey.pem;
add_header Strict-Transport-Security "max-age=15552000" always;
location ^~ /.well-known {
location = /.well-known/carddav { return 301 /remote.php/dav/; }
location = /.well-known/caldav { return 301 /remote.php/dav/; }
location /.well-known/acme-challenge { try_files $uri $uri/ =404; }
return 301 /index.php$request_uri;
}
location / {
try_files $uri $uri/ /index.php$request_uri;
}
}
Add includeSubDomains or preload to Strict-Transport-Security only after every subdomain that users may visit is ready to stay on HTTPS. Browser HSTS caching can outlive a quick rollback.
$ sudo nginx -t nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful
Related: How to test Nginx configuration
$ sudo systemctl reload nginx
Related: How to manage the Nginx service
$ sudo -E -u www-data php /var/www/nextcloud/occ config:system:set overwrite.cli.url --value=https://cloud.example.com System config value overwrite.cli.url set to string https://cloud.example.com
Generated links from command-line jobs should use the same public HTTPS URL that users open in a browser. If Nextcloud is behind another TLS terminator, also review trusted_proxies and overwrite settings.
Related: How to configure Nextcloud behind a reverse proxy
$ curl -sS https://cloud.example.com/status.php
{"installed":true,"maintenance":false,"needsDbUpgrade":false,"versionstring":"35.0.0","productname":"Nextcloud"}
A JSON response from https://cloud.example.com/status.php proves the public HTTPS request reaches the Nextcloud application instead of only proving that Nginx accepts TLS.
$ echo | openssl s_client -servername cloud.example.com -connect cloud.example.com:443 2>/dev/null | openssl x509 -noout -issuer -subject -dates issuer=C=US,O=Let's Encrypt,CN=E8 subject=CN=cloud.example.com notBefore=Jun 28 02:18:22 2026 GMT notAfter=Sep 26 02:18:21 2026 GMT
The intermediate name can differ by key type and issuer profile. The subject or SAN coverage, issuer organization, and validity dates should match the certificate just issued.
Tool: SSL Expiry Checker
$ systemctl list-timers certbot.timer NEXT LEFT LAST PASSED UNIT ACTIVATES Mon 2026-06-29 12:05:00 UTC 8h left Mon 2026-06-29 00:05:00 UTC 3h ago certbot.timer certbot.service
snap installs use snap-managed renewal units instead of the distro certbot.timer name.
$ sudo certbot renew --dry-run Saving debug log to /var/log/letsencrypt/letsencrypt.log ##### snipped ##### Congratulations, all simulated renewals succeeded: /etc/letsencrypt/live/cloud.example.com/fullchain.pem (success)
The dry run uses the staging service and does not replace the active production certificate.
$ sudo -E -u www-data php /var/www/nextcloud/occ setupchecks security: ✓ HTTPS access and URLs: You are accessing your instance over a secure connection, and your instance is generating secure URLs. config: ✓ Overwrite CLI URL: The "overwrite.cli.url" option in your config.php is correctly set to "https://cloud.example.com". ##### snipped #####
Review Administration settings → Overview in the browser as well, because that page is where administrators normally see remaining HTTPS, HSTS, proxy, or generated-URL warnings.
Related: How to check Nextcloud administration overview warnings