VPS Server Setup
This guide ensures Nginx never references non-existent certificates. We:
- create a minimal HTTP vhost to answer ACME HTTP-01 challenges,
- obtain certificates via Certbot (webroot),
- enable the HTTPS vhost with Basic Auth and your MkDocs static files,
- 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 :
| 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:
| # 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:
| 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:
| 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
| # 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.