Skip to content

VPS Server Setup

This guide ensures Nginx never references non-existent certificates. We:

  1. create a minimal HTTP vhost to answer ACME HTTP-01 challenges,
  2. obtain certificates via Certbot (webroot),
  3. enable the HTTPS vhost with Basic Auth and your MkDocs static files,
  4. keep HTTP only for ACME (and to redirect all other requests to HTTPS).

Prerequisites

  • Domain: docs.example.com → A record to your server’s public IP.
  • Ports 80 and 443 open from the Internet.
  • sudo access.
  • Your MkDocs site builds to site/ locally.

1) System preparation

Update and software installation :

1
2
3
4
5
6
7
8
9
sudo apt update && sudo apt upgrade -y

# Web + tools
sudo apt install -y nginx certbot python3-certbot-nginx apache2-utils rsync python3-venv

# Optional: firewall
# sudo apt install -y ufw
# sudo ufw allow 'Nginx Full'
# sudo ufw reload

Create directories:

1
2
3
4
5
6
# Your static site destination (Nginx will serve from here)
sudo mkdir -p /var/www/mkdocs_site

# ACME webroot used by Certbot for HTTP-01 challenges
sudo mkdir -p /var/www/letsencrypt
sudo chown -R www-data:www-data /var/www/letsencrypt

Build and deploy your MkDocs site:

python3 -m venv venv
source venv/bin/activate
pip install mkdocs-material
mkdocs new mkdocs_site
# In your mkdocs project:
cd mkdocs_site
mkdocs build
# Deploy to the server path:
sudo rsync -a site/ /var/www/mkdocs_site/
sudo chown -R www-data:www-data /var/www/mkdocs_site
sudo find /var/www/mkdocs_site -type d -exec chmod 755 {} \;
sudo find /var/www/mkdocs_site -type f -exec chmod 644 {} \;

2) Minimal HTTP vhost (for ACME challenge)

At this stage, do not configure HTTPS or Basic Auth yet.

Create /etc/nginx/sites-available/vps.b0rg.fr:

# HTTP-only for initial certificate issuance
server {
    listen 80;
    listen [::]:80;
    server_name vps.b0rg.fr;

    # ACME challenge location (no auth, must be publicly reachable)
    location ^~ /.well-known/acme-challenge/ {
        root /var/www/letsencrypt;
        default_type "text/plain";
        allow all;
    }

    # For everything else, you can show a simple 404 (or temporary message)
    location / {
        return 404;
    }
}

Enable and reload:

1
2
3
sudo ln -s /etc/nginx/sites-available/vps.b0rg.fr /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Confirm that http://vps.b0rg.fr/.well-known/acme-challenge/test returns 404 (but the server answers). This proves port 80 is reachable.

curl http://vps.b0rg.fr/.well-known/acme-challenge/test

3) Issue Let’s Encrypt certificate (webroot)

Run Certbot with the webroot you created:

1
2
3
4
sudo certbot certonly --webroot \
  -w /var/www/letsencrypt \
  -d vps.b0rg.fr \
  --agree-tos -m zenetro@gmail.com --non-interactive

If successful, your certs live under:

/etc/letsencrypt/live/vps.b0rg.fr/fullchain.pem
/etc/letsencrypt/live/vps.b0rg.fr/privkey.pem

You can list them:

sudo ls -l /etc/letsencrypt/live/vps.b0rg.fr/

4) Create Basic Auth credentials

1
2
3
4
5
6
7
8
# Create password file and first user
sudo htpasswd -c /etc/nginx/.htpasswd username1
# Add more users later (without -c)
# sudo htpasswd /etc/nginx/.htpasswd user2

# Restrict permissions
sudo chown root:www-data /etc/nginx/.htpasswd
sudo chmod 640 /etc/nginx/.htpasswd

5) Final HTTPS vhost (enable TLS + Basic Auth)

Replace the previous file /etc/nginx/sites-available/vps.b0rg.fr with the final config below (HTTP remains for ACME and redirect; HTTPS serves your site with Basic Auth):

# -------------------
# HTTP: keep for ACME + redirect everything else to HTTPS
# -------------------
server {
    listen 80;
    listen [::]:80;
    server_name vps.b0rg.fr;

    # ACME challenge must remain accessible and unauthenticated
    location ^~ /.well-known/acme-challenge/ {
        root /var/www/letsencrypt;
        default_type "text/plain";
        allow all;
        auth_basic off;
    }

    # Redirect all other requests to HTTPS
    location / {
        return 301 https://$host$request_uri;
    }
}

# -------------------
# HTTPS: serve MkDocs + Basic Auth
# -------------------
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name vps.b0rg.fr;

    # --- TLS (we already have certs) ---
    ssl_certificate     /etc/letsencrypt/live/vps.b0rg.fr/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/vps.b0rg.fr/privkey.pem;

    # Recommended defaults installed by Certbot (present after first issuance)
    # include /etc/letsencrypt/options-ssl-nginx.conf;
    # ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:10m;
    ssl_prefer_server_ciphers off;
    # (Optional) tune ciphers if you have a requirement; otherwise defaults are fine on modern Nginx/OpenSSL.

    # --- Basic Auth protects the whole site ---
    auth_basic "Restricted";
    auth_basic_user_file /etc/nginx/.htpasswd;

    # Serve MkDocs static site
    root /var/www/mkdocs_site;
    index index.html;

    # Client-side routing safety (serves index.html when a path isn't a file)
    location / {
        try_files $uri $uri/ /index.html;
    }

    # Static caching (optional)
    location ~* \.(?:css|js|png|jpg|jpeg|gif|svg|ico|woff2?)$ {
        expires 7d;
        add_header Cache-Control "public";
        try_files $uri =404;
    }

    # (Strictly not needed for HTTP-01, but harmless)
    location ^~ /.well-known/acme-challenge/ {
        root /var/www/letsencrypt;
        default_type "text/plain";
        allow all;
        auth_basic off;  # ensure no auth accidentally blocks challenges
    }
}

Reload Nginx:

sudo nginx -t
sudo systemctl reload nginx

Browse to https://vps.b0rg.fr — you should be prompted for username/password, then see your MkDocs site.

6) Certbot auto-renewal

Test renewal:

sudo certbot renew --dry-run

Certbot installs a systemd timer that renews automatically. Because we used webroot and kept the /.well-known/acme-challenge/ location unauthenticated on port 80, renewals will work seamlessly without disabling Basic Auth for the site itself.