If you SSH in and hand-edit the Nginx server block to tweak your Ploi ssl setup, your changes vanish the next time anyone touches that site in the Ploi panel. Ploi owns that file. It regenerates the main server block from a template, writes the `ssl_certificate` and `ssl_certificate_key` lines itself when it installs the Let's Encrypt cert, and pulls extra behaviour in through generated include files. Edit the managed block directly and a redeploy overwrites it, or worse, your manual `listen 443 ssl` collides with Ploi's and `nginx -t` fails. So I never touch the managed block. I put my directives in Ploi's custom Nginx config include, the one file it appends to but never rewrites.
How Ploi structures the Nginx config
Each site Ploi provisions gets one main config file at `/etc/nginx/sites-available/<your-domain>`, symlinked into `sites-enabled`. That file is template-generated: it holds the `server` blocks, the `listen` directives, the document root, the PHP-FPM `fastcgi_pass`, and, once you enable SSL, the certificate lines. The detail that trips people up is that this file is not yours. Ploi rewrites it whenever you change the PHP version, toggle SSL, change the web directory, or deploy certain settings. Anything you typed by hand inside it is gone the moment Ploi regenerates.
To leave you a safe seam, Ploi has the generated block `include` separate files it creates but never templates over. These live under `/etc/nginx/ploi/<your-domain>/`, split into three folders by where they land relative to the `server` block: `before/` (above the block, http context), `server/` (inside the block), and `after/` (below it, for things like a separate redirect host). The panel's "Custom Nginx config" box writes into the `server/` folder, so its contents end up inside the `server { ... }` context. Whatever you save in the panel lands there, and the main block keeps including it across every regeneration.
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name yourdomain.com;
root /home/ploi/yourdomain.com/public;
# Ploi writes these two lines when it installs the cert.
# Hand-editing them is pointless: they get regenerated.
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
# ... fastcgi_pass, index, gzip, etc ...
# The seam Ploi leaves for you, inside the server block.
# THIS is where the panel's custom config gets included.
include /etc/nginx/ploi/yourdomain.com/server/*.conf;
}Where my custom SSL directives go instead
In the Ploi panel I open the site, go to Manage > Nginx configuration (the "Custom Nginx config" editor), and put my directives there. Save, and Ploi writes the file into the site's `server/` folder and reloads Nginx. Because that content sits inside the `server { ... }` block via the include, you write directives, not a new `server` block. That single distinction is what keeps you out of trouble, and pasting a full server block here is the most common way people break their site.
The usual reason to come here is hardening the TLS handshake or adding security headers the default template does not set. This is valid include-level content because every line is a directive that belongs inside an existing `server` context:
# HSTS - tell browsers to stick to HTTPS for a year.
# Only add this once every subdomain is on TLS.
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Tighten the protocol floor above the distro default.
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
# A larger upload ceiling for this app.
client_max_body_size 64M;Why nginx -t fails after you add your own block
The classic mistake is pasting a complete `server { listen 443 ssl; ... }` into the custom config box, copied from some hardening guide. Now you have two server blocks claiming the same name on the same port, or two `listen 443 ssl` directives, or a second `ssl_certificate` line fighting the one Ploi already wrote. The reload Ploi triggers on save runs `nginx -t` first, and you get one of these:
- "duplicate listen options for [::]:443" - your pasted block re-declares a listen directive the managed block already owns.
- "a duplicate default server for 0.0.0.0:443" - your block also carries default_server.
- "conflicting server name yourdomain.com on 0.0.0.0:443, ignored" - a second server block with the same server_name.
- "\"ssl_certificate\" directive is duplicate" - you set the cert path again on top of Ploi's.
I diagnose it the same way every time: SSH in and ask Nginx directly rather than guessing from the panel. `nginx -t` names the exact file and line of the clash, and dumping the merged config shows the generated block and your include side by side, so you can see the duplicate yourself:
# Does the config parse? Names the file + line of any clash.
sudo nginx -t
# See the fully merged config - generated block AND your include together.
sudo nginx -T | less
# After a clean test, reload without dropping connections.
sudo systemctl reload nginxThe fix is almost always to delete everything except the bare directives. You do not need `server`, you do not need `listen`, you do not need the certificate lines. Ploi already supplied all of it. If a directive genuinely cannot live in the `server` context Ploi gives you - something that must sit in `http`, or a separate redirect host that needs its own `server` block - drop it in the `before/` or `after/` folder under `/etc/nginx/ploi/<your-domain>/` (or a file under `/etc/nginx/conf.d/`), not in the per-site custom box. This is the same duplicate-directive problem I walk through for raw servers in Nginx + Certbot SSL conflicts, and the include-ordering logic mirrors what I cover in the Nginx reverse proxy configuration writeup.
On Ploi, the server block is not your file. Treat it as build output and put every change in the include, or accept that your next deploy will quietly erase your work.
How renewal works, and what to check when it fails
Ploi installs Let's Encrypt certificates with Certbot and renews them for you from the system crontab, not from a `certbot.timer` you manage. The renewal cron runs Certbot and logs to `/home/ploi/.ploi/certbot-renew.log`, which you can also read from the server's Logs tab in the panel. So when a renewal fails, the cause is rarely "Certbot is broken"; it is usually something blocking the HTTP-01 validation. I work through this list before opening a support ticket:
- DNS: the domain's A/AAAA record must still point at this server's IP. A cert cannot renew for a hostname that now resolves elsewhere - check your registrar.
- Port 80 reachable: HTTP-01 validation hits http://yourdomain.com/.well-known/acme-challenge/. If a firewall or security group closed port 80, validation fails even though the site works fine on 443.
- Basic auth or a redirect that swallows /.well-known/: if HTTP basic auth is on, or a custom include adds a catch-all redirect that captures the ACME path, the challenge 401s or 404s. Exempt /.well-known/acme-challenge/ from auth and redirects.
- The config still parses: run sudo nginx -t. A broken include stops the reload that follows renewal, so the new cert lands on disk but Nginx never picks it up.
- The renewal log: read /home/ploi/.ploi/certbot-renew.log. Certbot's own output names the exact failure 99% of the time.
I confirm what is actually installed straight from the filesystem, because the panel shows what Ploi believes is true and the disk shows what Nginx is really serving. The live cert and key sit under `/etc/letsencrypt/live/<domain>/`, and you can read the real expiry out of the certificate without trusting any dashboard:
# What is the actual expiry on the cert Nginx is serving?
sudo openssl x509 -enddate -noout \
-in /etc/letsencrypt/live/yourdomain.com/fullchain.pem
# Is the ACME challenge path reachable over plain HTTP, not redirected?
curl -sI http://yourdomain.com/.well-known/acme-challenge/test
# After Certbot reissues, confirm the running config still parses.
sudo nginx -tThe whole discipline reduces to one rule: on Ploi, the generated server block is build output, not source. Keep your hands off `/etc/nginx/sites-available/`, push every TLS header, protocol tweak, and upload limit through the custom Nginx config include, and let Ploi own the certificate and renewal lines it generates with Certbot. Do that and a redeploy is a non-event, because your customizations live in the one file Ploi was designed never to overwrite. The day you reach for `vim` on the managed block is the day you sign up to lose that change.

