Execute shell commands on specific events. Configure globally in YAML.
hooks:
shell: /bin/bash
hourly:
- echo "Hourly task"
prestart:
- echo "Starting GoBlog"
postpost:
- echo "New post at {{.URL}}"
postupdate:
- echo "Updated {{.URL}}"
postdelete:
- echo "Deleted {{.URL}}"
postundelete:
- echo "Undeleted {{.URL}}"Post hooks receive template variables:
.URL(string): The post's URL.Post(object): The post object with all post data
Use hooks for tasks like:
- Sending notifications to external services
- Triggering static site generators
- Backing up posts after changes
- Updating search indexes
Create custom redirects using regular expressions:
pathRedirects:
- from: "\\/index\\.xml"
to: ".rss"
type: 302
- from: "^\\/(old|legacy)\\/(.*)$"
to: "/new/$2"
type: 301The from field is a Go regular expression. The to field can use capture groups ($1, $2, etc.). The type field is optional (defaults to 302).
Redirect old URLs to new posts using the aliases front matter parameter:
---
path: /new-path
aliases:
- /old-path
- /another-old-path
---Old URLs redirect permanently (301) to the new post.
Use a custom short domain for compact post URLs:
server:
shortPublicAddress: https://short.example.comEvery post gets a short URL at /s/{hex-id} which redirects to the full post path.
Use a custom domain for serving media files:
server:
mediaAddress: https://media.example.com
cspDomains:
- media.example.com # Add to Content Security PolicyCustom domains (short URLs and media URLs) are global for the entire GoBlog instance. You cannot use different domains for different blogs on the same instance.
Run multiple blogs under one GoBlog installation. Common use cases:
- Multiple languages: Separate blogs for different languages
- Different content types: Personal vs. professional content
Example configuration:
defaultBlog: en
blogs:
en:
path: /
lang: en
# title and description are configured via the settings UI
de:
path: /de
lang: de
fr:
path: /fr
lang: frEach blog has its own:
- Title and description (configurable via settings UI)
- Sections and taxonomies
- Menus and navigation
- Settings and preferences
- ActivityPub actor (
@blogname@yourdomain.com)
All blogs share the same user account and global settings (server, database, etc.).
Response caching is enabled by default with a 6-hour TTL. Configure in YAML (see example-config.yml for options):
cache:
enable: true
expiration: 600 # Per-route override in secondsAppend ?cache=0 or ?cache=false to any URL to skip the cache for that request.
Run SQLite maintenance commands:
sqlite3 data/db.sqlite "VACUUM;"
sqlite3 data/db.sqlite "ANALYZE;"Configure external media storage (BunnyCDN, FTP) to offload media serving. See example-config.yml for configuration.
Enable hourly SQL dumps:
database:
dumpFile: data/db.sqlStop GoBlog, copy the data directory, restart:
# Docker
docker-compose down
cp -r data data-backup-$(date +%Y%m%d)
docker-compose up -d
# Binary
# Stop GoBlog, then:
cp -r data data-backup-$(date +%Y%m%d)
# Restart GoBlogTor Hidden Service
GoBlog can serve as a Tor .onion hidden service. Set server.tor: true in your YAML config. Tor must be installed and available in $PATH.
When enabled, GoBlog:
- Generates and persists an Ed25519 private key at
data/tor/onion.pk(the.onionaddress is stable across restarts) - Starts an onion service on port 80
- Adds an
Onion-LocationHTTP header to all responses, directing Tor browsers to the.onionaddress - Logs the
.onionaddress at startup
The .onion address is derived from the persistent key, so it remains the same across restarts. The cache is purged when Tor starts.
Two Docker images are available:
ghcr.io/jlelse/goblog:latest- Base imageghcr.io/jlelse/goblog:tools- Includessqlite3,bash,curland other tools for hook commands or advanced users
GoBlog serves a /ping endpoint that returns HTTP 200 when the server is running. Use this for external monitoring, load balancers, or container health checks.
# CLI health check
./GoBlog --config ./config/config.yml healthcheck
# Manual check
curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/pingPosts not publishing
- Check
status: publishedin front matter - Check
visibility: public(orunlisted) - For scheduled posts, ensure
publisheddate is in the past
ActivityPub not working
- Ensure
activityPub.enabled: true - Ensure
privateMode.enabled: false - Verify DNS resolves to your server
- Verify HTTPS is working properly
Media uploads failing
- Check available disk space
- Check file permissions on
data/media/ - For external storage, verify credentials
Application logs:
# Docker
docker-compose logs -f goblog
# Binary
./GoBlog --config ./config/config.ymlAccess logs:
server:
logging: true
logFile: data/access.logDebug mode:
debug: trueDatabase debug:
database:
debug: true # Log all SQL queriesRestrict all public access. When enabled, every page requires authentication and X-Robots-Tag: noindex is set on all responses:
privateMode:
enabled: trueWhen securityHeaders: true (auto-enabled with HTTPS), GoBlog sets: Strict-Transport-Security (1 year), Referrer-Policy: no-referrer, X-Content-Type-Options: nosniff, and a Content Security Policy. The CSP includes frame-ancestors 'none' (blocks all framing). See configuration.md for CSP domain configuration.
Cookies are set as Secure when security headers are enabled or the public address uses HTTPS. A 100 MB body size limit is enforced on all requests.
Enable HTTP access logging:
server:
logging: true
logFile: data/access.logAccess logs are rotated daily and include timestamp, method, path, status code, and response time, but do not log client IP addresses for privacy reasons.
Enable verbose logging:
debug: trueEnable SQL query logging:
database:
debug: true # Log all SQL queriesEnable Go's built-in profiling endpoints for performance analysis:
pprof:
enabled: true
address: localhost:6060 # Optional, defaults to random port on localhostWhen enabled, GoBlog starts a separate HTTP server with pprof endpoints:
/debug/pprof/- Profiling index page/debug/pprof/profile- CPU profile (30-second sample)
The pprof server runs on a separate listener (not the main server) and is opt-in only. For CLI-based profiling, use the --cpuprofile and --memprofile flags.