🦞 Clawdie and Nginx

Everything learned about serving static files on the domedog.pro server • 06.03.2026

TL;DR: nginx runs as www-data. Our server umask is 0007, so new files get permissions 660 (no world-read). The fix: add www-data to the clawdie group once — all future files work automatically.

1. Server Layout

DetailValue
Serverdomedog.pro
OS userclawdie
Web servernginx (system service, runs as www-data)
Web root/home/clawdie/htdocs/domedog.pro/
nginx config/etc/nginx/sites-enabled/domedog.pro.conf
SSLLet's Encrypt via /etc/letsencrypt/live/domedog.pro/
HTTP→HTTPS301 redirect (port 80 → 443)

Active nginx config

server {
    listen 80;
    listen [::]:80;
    server_name domedog.pro;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name domedog.pro;

    ssl_certificate /etc/letsencrypt/live/domedog.pro/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/domedog.pro/privkey.pem;

    root /home/clawdie/htdocs/domedog.pro;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

2. The 404 Problem — Root Cause

When a new HTML file was created by Clawdie (writing as user clawdie), it got a 404 in the browser even though the file existed on disk.

Symptom: File exists at /home/clawdie/htdocs/domedog.pro/page.html but https://domedog.pro/page.html returns 404.

Why this happens — the umask chain

Step 1 — Check the umask:

$ umask
0007

Umask 0007 means: remove write+exec from group, remove all permissions from others. New files get mode 660 (rw-rw----), new directories get 770 (rwxrwx---).

Step 2 — Check who nginx runs as:

$ id www-data
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Step 3 — Check file permissions:

$ ls -la /home/clawdie/htdocs/domedog.pro/page.html
-rw-rw---- 1 clawdie clawdie 23916 Mar 6 09:34 page.html
#  ^^^ 660: owner+group can read, others (www-data) CANNOT

Conclusion: nginx (www-data) is not in the clawdie group, so it gets "others" permissions — which is nothing. nginx returns 404 (or 403) because it cannot read the file.

3. The Fix — Add www-data to the clawdie Group

✅ Applied once, works forever for all future files.
# Add www-data to the clawdie group
sudo usermod -a -G clawdie www-data

# Reload nginx to pick up the new group membership
sudo systemctl reload nginx

After this:

Security note: This gives www-data read access to all files on the system that have group clawdie and mode g+r. Since our umask is 0007, that means any file created by the clawdie user anywhere. Keep sensitive files in credentials/ with mode 600 (owner-only) to exclude them.

4. Debugging Checklist — When You Get a 404

Work through these in order:

1
Does the file actually exist?
ls -la /home/clawdie/htdocs/domedog.pro/your-file.html
2
Is nginx using the right root?
sudo nginx -T | grep -A20 "domedog"
# Look for: root /home/clawdie/htdocs/domedog.pro;
3
Can www-data read the file? (permission check)
ls -la /home/clawdie/htdocs/domedog.pro/your-file.html
# Need at least: -rw-r--r-- (644) OR -rw-rw---- (660) with www-data in clawdie group
stat -c "%a %G %n" /home/clawdie/htdocs/domedog.pro/your-file.html
# Should show: 644 clawdie ... OR 660 clawdie ... (with www-data in group)
4
Can www-data traverse the path to get there?
ls -ld /home/clawdie /home/clawdie/htdocs /home/clawdie/htdocs/domedog.pro
# All dirs need at least o+x (execute/traverse) or www-data in the group
5
Check nginx error log for the real reason
sudo tail -20 /var/log/nginx/error.log
# "Permission denied" = file exists, can't read (permissions issue)
# "No such file or directory" = wrong path or file doesn't exist
6
Reload nginx if you changed config
sudo nginx -t              # test config syntax first
sudo systemctl reload nginx   # apply without dropping connections

5. Quick Permission Fix (one-off file)

If you ever need to fix a single file quickly before the group membership kicks in (nginx requires restart/reload to pick up new group memberships for running worker processes):

# Make a single file world-readable
chmod 644 /home/clawdie/htdocs/domedog.pro/your-file.html

# Make all files in webroot world-readable at once
chmod 644 /home/clawdie/htdocs/domedog.pro/*.html

6. Understanding umask 0007

The server uses umask 0007 (set in ~/.bashrc). This is intentional — it makes all files private by default, only accessible to owner and group members.

What gets createdModeReadable by
File (default)660 (rw-rw----)owner + clawdie group members
Directory (default)770 (rwxrwx---)owner + clawdie group members
Sensitive file (manual)600 (rw-------)owner only
Public web file (after fix)660 (rw-rw----)owner + clawdie group (now includes www-data)
Why not just change umask to 022? That would make every new file world-readable by default, including .env, credentials/, session files, etc. The current approach (tight umask + group membership for nginx) is the right balance of security and convenience.

7. Files Currently Served

URLFileDescription
/index.htmlClawdie PRD — product requirements document
/stripe-agents-plan.htmlstripe-agents-plan.htmlStripe Agents implementation plan
/clawdie-openclaw-transition-plan.htmlclawdie-openclaw-transition-plan.htmlOpenClaw skills system deep dive
/clawdie-and-nginx.htmlclawdie-and-nginx.htmlThis document

8. Useful nginx Commands

# Test config syntax (always do this before reload)
sudo nginx -t

# Dump full merged config (great for debugging)
sudo nginx -T

# Reload (apply config changes, no downtime)
sudo systemctl reload nginx

# Restart (full restart, brief downtime)
sudo systemctl restart nginx

# Check status
sudo systemctl status nginx

# Watch error log live
sudo tail -f /var/log/nginx/error.log

# Watch access log live
sudo tail -f /var/log/nginx/access.log

# Check what groups www-data is in (verify the fix)
groups www-data
# Should show: www-data : www-data clawdie

9. SSL / Let's Encrypt

SSL certificates are managed by Certbot and stored at /etc/letsencrypt/live/domedog.pro/. They auto-renew via a system timer. To check expiry:

# Check certificate expiry
sudo certbot certificates

# Test renewal (dry run, no changes)
sudo certbot renew --dry-run

# Force renew now
sudo certbot renew --force-renewal
Note: HTTP (port 80) is redirected to HTTPS (port 443) via a 301 in nginx. All traffic is encrypted. There is no plain HTTP serving of content.