The objective for c2thulhu.sh was to establish a publishing platform with a minimal attack surface. Traditional Content Management Systems (CMS) often introduce unnecessary complexity, maintenance overhead, and a broad landscape of potential vulnerabilities.

To mitigate these risks, I implemented a “Secure-by-Design” architecture that prioritizes static content delivery, strict transport security, and minimized server-side dependencies. This post details the transition from a local Emacs workflow to a hardened, Go-based production environment.

The Architecture

The design philosophy relies on three core principles: Zero dynamic server-side processing, minimized dependencies, and strict edge defense.

The stack consists of:

  • Local Machine: Emacs + Org-Mode for content generation.
  • Static Generator: Hugo (Extended) to compile Markdown into pure HTML/CSS.
  • Transport: Git (GitHub) as the source of truth.
  • Web Server: Caddy (written in Go) replacing Nginx.
  • Edge Defense: Cloudflare (Strict SSL + DDoS protection).

The Local Workflow

Content is generated locally using Emacs’ Org-Mode. To enforce schema consistency and streamline frontmatter generation, I utilize a custom Emacs Lisp function. This eliminates manual formatting errors and standardizes the drafting process:

(defvar my/hugo-content-dir "~/Projects/MySecBlob/content-org/"
  "The directory where new post files will be created.")

(defun my/hugo-new-post (title section tags)
  "Create a new Org file for a Hugo post by inserting text directly.
   Prompts for Title, Section, and Tags."
  (interactive
   (list
    (read-string "Post Title: ")
    (read-string "Section (default 'posts'): " nil nil "posts")
    (read-string "Tags (comma separated): ")))

  (let* ((clean-title (downcase title))
         (slug (replace-regexp-in-string " " "-" clean-title))
         (slug (replace-regexp-in-string "[^a-z0-9-]" "" slug))
         (slug (replace-regexp-in-string "-+" "-" slug))
         (filename (concat (file-name-as-directory (expand-file-name my/hugo-content-dir))
                           slug ".org"))
         (date (format-time-string "%Y-%m-%d")))

    ;; Create + Open the file
    (find-file filename)
    (erase-buffer) ;; Ensure it's empty if I just created it

    ;; Insert File-Level Headers
    (insert "#+HUGO_BASE_DIR: ../\n")
    (insert (format "#+HUGO_SECTION: %s\n\n" section))

    ;; Insert Headline manually (Safer than org-insert-heading)
    (insert (format "* DRAFT %s\n" title))

    ;; Insert Properties Drawer manually
    (insert ":PROPERTIES:\n")
    (insert (format ":EXPORT_FILE_NAME: %s\n" slug))
    (insert (format ":EXPORT_DATE: %s\n" date))

    ;; Only insert tags if I actually typed some
    (when (not (string= tags ""))
      (insert (format ":EXPORT_HUGO_TAGS: %s\n" tags)))

    (insert ":END:\n\n")

    ;; Save and position cursor
    (save-buffer)
    (message "Created new post: %s" filename)))

    ;; Bind it to a key (C-c S-n) for speed
    (global-set-key (kbd "C-c M-n") 'my/hugo-new-post)
  )

This ensures every post follows the same strict schema before it ever hits the compiler.

Dependency Management

A compatibility mismatch arose between the operating system’s default repositories and the theme requirements. The PaperMod theme requires Hugo v0.146+, while standard repositories often serve outdated binaries (e.g., v0.131).

To resolve template rendering errors, the environment was configured using the latest Hugo Extended binary, manually deployed to ensure support for advanced SCSS processing features.

#+begin_src bash wget https://github.com/gohugoio/hugo/releases/download/v0.146.0/hugo_extended_0.146.0_linux-amd64.deb dpkg -i hugo_extended_0.146.0_linux-amd64.deb #+end_src

Web Server Configuration

Caddy was selected over Nginx for its memory safety (Go) and concise configuration syntax. However, a specific challenge exists when provisioning SSL behind a reverse proxy.

Since Cloudflare proxies traffic, Caddy cannot complete the standard ACME challenge for Let’s Encrypt. This results in SSL handshake failures (ERR_SSL_VERSION_OR_CIPHER_MISMATCH).

The Solution: Cloudflare Origin Certificates

To resolve this, I implemented Cloudflare Origin Certificates. This establishes a strict chain of trust:

  • Client to Edge: Publicly trusted Cloudflare SSL.
  • Edge to Origin: Authenticated via the Origin Certificate (valid for 15 years).

This configuration allows Caddy to serve secure traffic without external validation requests. The resulting Caddyfile is minimal and includes hardened security headers:

c2thulhu.sh:443, www.c2thulhu.sh:443 {
    root * MySecBlob/public
    file_server
    tls <redacted>.pem <redacted>.key

    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        X-Frame-Options "DENY"
        X-Content-Type-Options "nosniff"
        Referrer-Policy "strict-origin-when-cross-origin"
        X-XSS-Protection "1; mode=block"
        -Server
    }
}

Automating Deployment

To maintain synchronization between the repository and the production environment, a bash-based deployment script handles the build lifecycle:

#!/bin/bash

# Define the project directory
PROJECT_DIR="/var/www/MySecBlob"

# Log everything to a file so I can debug if it breaks
LOG_FILE="/var/log/blog_update.log"

{
    echo "=========================================="
    echo "Starting update: $(date)"

    # Go to the project directory
    cd "$PROJECT_DIR" || exit

    # Pull the latest changes from Git
    # I use reset --hard to force your server to match GitHub exactly
    echo "Pulling from Git..."
    git fetch --all
    git reset --hard origin/main
    git submodule update --init --recursive

    # Rebuild the Hugo site
    # This generates the new HTML in the /public folder
    echo "Building Hugo site..."
    /usr/bin/hugo --minify

    # Restart Caddy to serve the new files
    # (Restart is safer than reload here to ensure SSL certs are loaded correctly)
    echo "Restarting Caddy..."
    systemctl restart caddy

    echo "Update Complete: $(date)"
    echo "=========================================="

} >> "$LOG_FILE" 2>&1

This script is executed via cron twice daily (0 0,12 * * *), ensuring the site remains current without manual intervention.

Conclusion

This architecture delivers a highly performant platform with negligible hosting costs and a significantly reduced threat landscape. By removing server-side dynamic processing and database dependencies, common web vulnerability classes are effectively mitigated.

For documentation and technical writing, a static architecture offers the optimal balance of security and maintainability.