Clawdie AI

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.

Step 1

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
Step 2

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;
    }
}
Note

The return 444 on the default server drops connections to unrecognized hostnames — no response, no information leak.

Step 3

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
Step 4

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"
Auto-renewal

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
Step 5

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;
    }
}
DNS reminder

Don't forget to add the DNS A/AAAA record for the subdomain before requesting the SSL certificate.

Security headers

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

CommandPurpose
nginx -tTest configuration syntax
service nginx reloadReload without downtime
service nginx restartFull restart
tail -f /var/log/nginx/error.logWatch error log
tail -f /var/log/nginx/access.logWatch access log
acme.sh --listList managed certificates
acme.sh --renew -d clawdie.siForce certificate renewal