BLACKBOXHelp Center

Web server with HTTPS: nginx or Apache and Let's Encrypt

Installing nginx or Apache, setting up a site for your domain and getting a free Let's Encrypt certificate with certbot.

generalUpdated 2026-10-09~12 min read
On this page

This guide shows you how to publish a site on your BLACKBOX server: install the nginx or Apache web server, set up the site for your domain and get a free HTTPS certificate from Let's Encrypt with certbot. You work in the server's terminal, connected over SSH as root (Connecting to your server over SSH (Windows, macOS, Linux)). The app.blackbox.md panel has no section for web servers or certificates: all you need from it is the server's IP address, shown on the server card on the “My Servers” page (app.blackbox.md/my-servers).

Available to: anyone with root access over SSH to a BLACKBOX server running Ubuntu 24.04, 22.04 or 20.04, Debian 12 or 11, AlmaLinux 9 or CentOS Stream 9.

In the commands, replace DOMAIN with your domain without www (for example example.com). Choose only one web server, nginx or Apache: both use ports 80 and 443 and can't run at the same time.

If you have a “VPS hosting” plan with a control panel, don't install another web server on top of the panel: you set up sites and certificates in the control panel. If you don't know which panel you have or how to sign in to it, write to support. See “VPS hosting” plans with a control panel.

Before you start

Requirement Details
The domain points to the server the A records for DOMAIN and www.DOMAIN have the value SERVER_IP, and there is no AAAA record left over from your old hosting; see How to point your domain to the server
Ports 80 and 443 are open on a new server, BLACKBOX network filtering allows SSH (22), HTTP (80), HTTPS (443) and ping; the operating system's firewall (ufw, firewalld) is not enabled
Ports 80 and 443 are free no other program (such as another web server) is listening on them

On the server, check which programs are listening on ports 80 and 443. On a new server, the command shows nothing:

ss -tlnp | grep -E ':80|:443'

If you have enabled the operating system's firewall yourself, allow HTTP and HTTPS. On Ubuntu and Debian (ufw):

ufw allow 80,443/tcp

On AlmaLinux and CentOS Stream (firewalld):

firewall-cmd --permanent --add-service=http --add-service=https
firewall-cmd --reload

Don't enable the firewall just for this guide. If you want to enable it, allow SSH first and keep a second SSH session open so you don't lose access: Firewall: network filtering and ufw/firewalld on the server.

Option 1: nginx

Ubuntu and Debian

  1. Install nginx. The service starts automatically after installation.

    apt update
    apt install -y nginx
  2. Create the site's directory and a test page:

    mkdir -p /var/www/DOMAIN
    echo "DOMAIN OK" > /var/www/DOMAIN/index.html
  3. Create the site's configuration file:

    nano /etc/nginx/sites-available/DOMAIN
  4. Paste the content below, save with Ctrl+O and Enter, then exit with Ctrl+X:

    server {
        listen 80;
        server_name DOMAIN www.DOMAIN;
        root /var/www/DOMAIN;
        index index.html index.htm;
    
        location / {
            try_files $uri $uri/ =404;
        }
    }
  5. Enable the site:

    ln -s /etc/nginx/sites-available/DOMAIN /etc/nginx/sites-enabled/
  6. Check the configuration and reload nginx. nginx -t must show “syntax is ok” and “test is successful”; if it shows an error, fix the file before you reload.

    nginx -t
    systemctl reload nginx

AlmaLinux and CentOS Stream

  1. Install and start nginx:

    dnf install -y nginx
    systemctl enable --now nginx
  2. Create the site's directory and the test page, then apply the SELinux label for web content: files without it return “403 Forbidden”. Run restorecon again whenever you move (mv) files into the directory from another location.

    mkdir -p /var/www/DOMAIN
    echo "DOMAIN OK" > /var/www/DOMAIN/index.html
    restorecon -Rv /var/www
  3. Create the configuration file (if nano is missing, the first command installs it):

    dnf install -y nano
    nano /etc/nginx/conf.d/DOMAIN.conf
  4. Paste the same server { … } block as for Ubuntu and Debian, save and exit.

  5. Check the configuration and reload nginx:

    nginx -t
    systemctl reload nginx

Option 2: Apache

Ubuntu and Debian

  1. Install Apache. The service starts automatically after installation.

    apt update
    apt install -y apache2
  2. Create the site's directory and the test page with the commands from step 2 of the nginx option.

  3. Create the site's configuration file:

    nano /etc/apache2/sites-available/DOMAIN.conf
  4. Paste the content below, save and exit:

    <VirtualHost *:80>
        ServerName DOMAIN
        ServerAlias www.DOMAIN
        DocumentRoot /var/www/DOMAIN
        ErrorLog ${APACHE_LOG_DIR}/DOMAIN-error.log
        CustomLog ${APACHE_LOG_DIR}/DOMAIN-access.log combined
    </VirtualHost>
  5. Enable the site, check the configuration and reload Apache. apachectl configtest must show “Syntax OK”.

    a2ensite DOMAIN.conf
    apachectl configtest
    systemctl reload apache2

AlmaLinux and CentOS Stream

  1. Install Apache (httpd) and the HTTPS module (mod_ssl, which certbot needs), then start the service:

    dnf install -y httpd mod_ssl
    systemctl enable --now httpd
  2. Create the site's directory and the test page with the commands from step 2 of the nginx option, including restorecon.

  3. Create the site's configuration file (if nano is missing, install it as in step 3 of the nginx option):

    nano /etc/httpd/conf.d/DOMAIN.conf
  4. Paste the <VirtualHost *:80> block from the Ubuntu and Debian steps, with different log paths: /var/log/httpd/DOMAIN-error.log and /var/log/httpd/DOMAIN-access.log. Save and exit.

  5. Check the configuration and reload Apache:

    apachectl configtest
    systemctl reload httpd

Checking the site over HTTP

On your computer, open http://DOMAIN in a browser: you should see the text “DOMAIN OK”. Or run the command below (on Windows, in PowerShell, type curl.exe instead of curl); the response must start with HTTP/1.1 200 OK. If the site doesn't respond, fix that first: certbot needs a site that is reachable over HTTP.

curl -I http://DOMAIN

Let's Encrypt certificate with certbot

Installing certbot

Ubuntu and Debian. For Apache, install python3-certbot-apache instead of python3-certbot-nginx.

apt install -y certbot python3-certbot-nginx

AlmaLinux and CentOS Stream. Certbot comes from the EPEL repository. The last command starts the renewal timer, which isn't turned on automatically on these systems. For Apache, install python3-certbot-apache.

dnf install -y epel-release
dnf install -y certbot python3-certbot-nginx
systemctl enable --now certbot-renew.timer

On Ubuntu and Debian, you can also use the snap package, which the certbot developers recommend: it is usually a newer version and includes the nginx and Apache plugins. Use either apt or snap, not both. If you have already installed certbot with apt, remove it:

apt remove -y certbot

On Debian, install snapd first:

apt install -y snapd
snap install core

Then install certbot from snap:

snap install --classic certbot
ln -s /snap/bin/certbot /usr/bin/certbot

Getting the certificate

  1. Run certbot with the plugin for your web server. For nginx:

    certbot --nginx -d DOMAIN -d www.DOMAIN

    For Apache:

    certbot --apache -d DOMAIN -d www.DOMAIN
  2. On the first run, certbot asks for an email address and for you to accept the Let's Encrypt terms: enter the address and answer Y. When asked about the EFF newsletter, you can answer N.

  3. If certbot asks whether to redirect HTTP to HTTPS (older versions do), choose “2: Redirect”. Newer versions add the redirect without asking.

  4. At the end, you see a message that starts with “Congratulations!”. The certificate and key are in /etc/letsencrypt/live/DOMAIN/, and certbot has added the HTTPS block (port 443) and the redirect from HTTP to the site's configuration.

  5. Open https://DOMAIN in your browser and check for the padlock. The command below shows your certificates, their domains and the expiry date (“Expiry Date”):

    certbot certificates

Automatic renewal

The certificate is valid for 90 days. The certbot timer checks the certificates twice a day and renews them on its own before they expire. With --nginx and --apache, certbot also reloads the web server after renewal.

How you installed certbot Renewal timer Started automatically
apt (Ubuntu, Debian) certbot.timer yes
snap snap.certbot.renew.timer yes
EPEL (AlmaLinux, CentOS Stream) certbot-renew.timer no: start it with systemctl enable --now certbot-renew.timer

Check the timer and simulate a renewal. --dry-run doesn't issue a new certificate and ends with “Congratulations, all simulated renewals succeeded” (in older versions, “Congratulations, all renewals succeeded”).

systemctl list-timers | grep -i certbot
certbot renew --dry-run

Note Let's Encrypt no longer sends emails before certificates expire, and it has announced that it will gradually shorten how long certificates are valid. Don't schedule manual renewals by the calendar: let the certbot timer do the work, and check the expiry date from time to time with certbot certificates.

Wildcard certificate (*.DOMAIN) with DNS validation

A wildcard certificate covers all subdomains one level down (www.DOMAIN, api.DOMAIN and so on), and you can only get one with DNS validation: certbot temporarily creates the TXT record _acme-challenge.DOMAIN. To keep renewal automatic, certbot needs a plugin for your DNS provider. The example below is for Cloudflare; for other providers, see the list of DNS plugins in the certbot documentation. Without a plugin (--manual), the certificate is no longer renewed automatically. If you need a wildcard, follow the steps below instead of the ones in “Getting the certificate”.

  1. In your Cloudflare account, create a token just for certbot: “My Profile” → “API Tokens” → “Create Token” → the “Edit zone DNS” template. Under “Zone Resources”, select only the DOMAIN zone, create the token and copy it. Don't reuse the token connected on the panel's “DNS” page: you can revoke a separate token without affecting the panel.

  2. Install the Cloudflare plugin. Ubuntu and Debian:

    apt install -y python3-certbot-dns-cloudflare

    AlmaLinux and CentOS Stream:

    dnf install -y python3-certbot-dns-cloudflare

    If you use certbot from snap:

    snap set certbot trust-plugin-with-root=ok
    snap install certbot-dns-cloudflare

    On Ubuntu 20.04, the apt plugin is too old and doesn't support API tokens: use certbot and the plugin from snap.

  3. Save the token in a file:

    mkdir -p /root/.secrets
    nano /root/.secrets/cloudflare.ini

    The file's content, with your token in place of CLOUDFLARE_TOKEN:

    dns_cloudflare_api_token = CLOUDFLARE_TOKEN
  4. Make the file accessible only to root:

    chmod 600 /root/.secrets/cloudflare.ini
  5. Get the certificate. --deploy-hook reloads the web server after each renewal; for Apache, write systemctl reload apache2 (Ubuntu, Debian) or systemctl reload httpd (AlmaLinux, CentOS Stream).

    certbot certonly --dns-cloudflare --dns-cloudflare-credentials /root/.secrets/cloudflare.ini -d DOMAIN -d "*.DOMAIN" --deploy-hook "systemctl reload nginx"
  6. certonly doesn't change the web server configuration: add an HTTPS block to the site's file yourself. For nginx, add the block below, then run nginx -t and reload nginx. For a subdomain, copy the block and change server_name and root; the certificate lines stay the same.

    server {
        listen 443 ssl;
        server_name DOMAIN www.DOMAIN;
        root /var/www/DOMAIN;
        ssl_certificate     /etc/letsencrypt/live/DOMAIN/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/DOMAIN/privkey.pem;
    }

    To redirect HTTP to HTTPS, in the block with listen 80, replace the root and index lines and the location block with return 301 https://$host$request_uri;.

  7. For Apache, add the block below to the site's file. To redirect HTTP to HTTPS, add the line Redirect permanent / https://DOMAIN/ to the <VirtualHost *:80> block.

    <VirtualHost *:443>
        ServerName DOMAIN
        ServerAlias www.DOMAIN
        DocumentRoot /var/www/DOMAIN
        SSLEngine on
        SSLCertificateFile    /etc/letsencrypt/live/DOMAIN/fullchain.pem
        SSLCertificateKeyFile /etc/letsencrypt/live/DOMAIN/privkey.pem
    </VirtualHost>

    On Ubuntu and Debian, enable the SSL module, check the configuration and restart Apache:

    a2enmod ssl
    apachectl configtest
    systemctl restart apache2

    On AlmaLinux and CentOS Stream, mod_ssl is already installed (step 1 of the Apache option):

    apachectl configtest
    systemctl reload httpd

Common problems

Message or symptom Cause What to do
“DNS problem: NXDOMAIN looking up A for DOMAIN” the A record is missing or hasn't propagated yet create the record, wait and check with dig +short DOMAIN A
“Timeout during connect (likely firewall problem)” port 80 is blocked or the domain points to a different address allow 80 and 443 in ufw/firewalld; check the A and AAAA records; if it still doesn't work, write to support
“Connection refused” the web server isn't running systemctl status nginx (or apache2, httpd) and ss -tlnp
“Invalid response from http://DOMAIN/.well-known/acme-challenge/…” with 404 the request reaches a different server: an old A or AAAA record, or the Cloudflare proxy fix the DNS records; turn off the proxy temporarily
“Could not automatically find a matching server block for DOMAIN” there is no server_name with this domain add server_name, run nginx -t, reload and run certbot again
“bind() to 0.0.0.0:80 failed (98: Address already in use)” another web server is using port 80 stop or remove it; keep only one web server
“too many failed authorizations” or “too many certificates” Let's Encrypt limits wait (see below); test with --dry-run
“403 Forbidden” on AlmaLinux or CentOS Stream the SELinux label on the files restorecon -Rv /var/www
“502 Bad Gateway” on AlmaLinux or CentOS Stream, with “(13: Permission denied)” in /var/log/nginx/error.log SELinux doesn't let nginx connect to the application behind it setsebool -P httpd_can_network_connect 1; see Node.js, Python and other services: nginx as a reverse proxy
ERR_TOO_MANY_REDIRECTS through Cloudflare the “Flexible” SSL mode in Cloudflare choose the “Full (strict)” mode

The full certbot log is in /var/log/letsencrypt/letsencrypt.log.

After you fix the cause, test issuance against the Let's Encrypt staging server, without getting a real certificate. --dry-run only works with certonly and renew; for Apache, write --apache.

certbot certonly --nginx --dry-run -d DOMAIN -d www.DOMAIN

Warning Let's Encrypt limits attempts: after 5 failed validations for the same name within an hour, new attempts are temporarily refused, and at most 5 certificates can be issued for exactly the same set of names in 7 days. Don't run certbot over and over: test with --dry-run, which doesn't count against these limits. Details: Let's Encrypt rate limits.

The domain goes through the Cloudflare proxy

  1. For the first issuance, keep the DOMAIN and www records on “DNS only” (on the panel's “DNS” page, the “Proxied” checkbox unchecked), or use the DNS validation described above.
  2. Once the site works over HTTPS directly on the server, turn on the proxy.
  3. In your Cloudflare account, under “SSL/TLS”, choose the “Full (strict)” encryption mode. The “Flexible” mode causes redirect loops, because the server redirects HTTP to HTTPS.

More about the proxy: How to point your domain to the server.

Next steps

Frequently asked questions

Can I host several sites on the same server? Yes. For each domain, create a separate directory and configuration file (a server block in nginx, a VirtualHost in Apache), then run certbot with that domain's names.

How do I add a subdomain to an existing certificate? Create the A record for the subdomain, add it to server_name (or ServerAlias), reload the web server and run certbot with all the names, the existing ones and the new one. For Apache, write --apache instead of --nginx:

certbot --nginx --expand -d DOMAIN -d www.DOMAIN -d app.DOMAIN

What happens if renewal fails? The current certificate stays valid until its expiry date, and the timer tries again twice a day. The cause is in /var/log/letsencrypt/letsencrypt.log; once you fix it, confirm with certbot renew --dry-run.

Can the AI assistant do these steps for me? Yes, if its token has the “Citește lista și statusul serverelor mele” (Read the list and status of my servers) (servers:read) and “Rulează orice comandă ca root pe serverele mele” (Run any command as root on my servers) (servers:exec) permissions. The domain must already point to the server. See Example tasks for the AI assistant and the permissions they need.

I followed the steps, but the site still doesn't open. Go through the checklist in The site does not open. If the problem persists, write to us: How to contact BLACKBOX support.

Didn't find your answer?Write to us and the support team will reply. support@blackbox.md
Open a ticket