Nginx + SSL
Web server configuration with Let's Encrypt on FreeBSD
Our nginx setup serves multiple sites from a single FreeBSD server — clawdie.si, osa.smilepowered.org, and docs.clawdie.si. All with HTTPS via Let's Encrypt, HTTP-to-HTTPS redirects, and clean vhost separation.
Installation
1.1 Install nginx
pkg install -y nginx
sysrc nginx_enable="YES"
1.2 Directory layout
/usr/local/etc/nginx/
├── nginx.conf # Main config
├── vhosts/
│ ├── clawdie.conf # clawdie.si
│ ├── osa.conf # osa.smilepowered.org
│ └── docs.clawdie.si.conf # docs.clawdie.si
├── ssl/
│ └── clawdie/
│ ├── fullchain.cer # Certificate chain
│ └── clawdie.key # Private key
└── mime.types
/usr/local/www/
├── clawdie/ # clawdie.si document root
│ ├── index.html
│ ├── css/
│ ├── docs/
│ └── guides/
├── docs.clawdie.si/ # docs.clawdie.si document root
│ ├── index.html
│ ├── css/
│ └── docs/
└── osa/ # osa.smilepowered.org
Main configuration
Edit /usr/local/etc/nginx/nginx.conf:
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
# Include all virtual hosts
include vhosts/*.conf;
# Default server — catch-all
server {
listen 80 default_server;
server_name _;
return 444;
}
}
The return 444 on the default server drops connections
to unrecognized hostnames — no response, no information leak.
Virtual host: clawdie.si
Create /usr/local/etc/nginx/vhosts/clawdie.conf:
# HTTP → HTTPS redirect
server {
listen 80;
server_name clawdie.si www.clawdie.si;
return 301 https://clawdie.si$request_uri;
}
# www → non-www redirect (HTTPS)
server {
listen 443 ssl;
server_name www.clawdie.si;
ssl_certificate /usr/local/etc/nginx/ssl/clawdie/fullchain.cer;
ssl_certificate_key /usr/local/etc/nginx/ssl/clawdie/clawdie.key;
return 301 https://clawdie.si$request_uri;
}
# Main site
server {
listen 443 ssl;
server_name clawdie.si;
root /usr/local/www/clawdie;
index index.html;
ssl_certificate /usr/local/etc/nginx/ssl/clawdie/fullchain.cer;
ssl_certificate_key /usr/local/etc/nginx/ssl/clawdie/clawdie.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
try_files $uri $uri/ =404;
}
}
Key decisions
- Non-www canonical: All traffic goes to
clawdie.si(not www) - HTTP → HTTPS: All plain HTTP redirects to secure
- TLS 1.2+: No older, insecure protocols
- Static serving: No application server needed for the site
SSL with Let's Encrypt
4.1 Install acme.sh
# acme.sh is a pure shell Let's Encrypt client
pkg install -y acme.sh
# Or install from source
curl https://get.acme.sh | sh
4.2 Issue certificate
# Using webroot validation
acme.sh --issue \
-d clawdie.si \
-d www.clawdie.si \
-w /usr/local/www/clawdie
4.3 Install certificate
mkdir -p /usr/local/etc/nginx/ssl/clawdie
acme.sh --install-cert \
-d clawdie.si \
--key-file /usr/local/etc/nginx/ssl/clawdie/clawdie.key \
--fullchain-file /usr/local/etc/nginx/ssl/clawdie/fullchain.cer \
--reloadcmd "service nginx reload"
acme.sh sets up a cron job automatically. Certificates renew every 60 days.
The --reloadcmd ensures nginx picks up the new cert.
4.4 Verify
# Test the configuration
nginx -t
# Reload
service nginx reload
# Check certificate
openssl s_client -connect clawdie.si:443 -servername clawdie.si < /dev/null 2>/dev/null | openssl x509 -noout -dates
Adding more sites
The vhost pattern makes it easy to add new sites. For example,
docs.clawdie.si as a public static documentation surface:
# /usr/local/etc/nginx/vhosts/docs.clawdie.si.conf
server {
listen 80;
server_name docs.clawdie.si;
return 301 https://docs.clawdie.si$request_uri;
}
server {
listen 443 ssl;
server_name docs.clawdie.si;
root /usr/local/www/docs.clawdie.si;
index index.html;
ssl_certificate /usr/local/etc/nginx/ssl/docs/fullchain.cer;
ssl_certificate_key /usr/local/etc/nginx/ssl/docs/docs.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
location /docs/ {
try_files $uri $uri/ /docs/index.html =404;
}
location / {
try_files $uri $uri/ =404;
}
}
Don't forget to add the DNS A/AAAA record for the subdomain before requesting the SSL certificate.
Apply a small baseline to every public vhost: X-Content-Type-Options,
X-Frame-Options, X-XSS-Protection, and
Referrer-Policy. It is low-effort hardening worth standardising.
Useful commands
| Command | Purpose |
|---|---|
nginx -t | Test configuration syntax |
service nginx reload | Reload without downtime |
service nginx restart | Full restart |
tail -f /var/log/nginx/error.log | Watch error log |
tail -f /var/log/nginx/access.log | Watch access log |
acme.sh --list | List managed certificates |
acme.sh --renew -d clawdie.si | Force certificate renewal |