Bludit is a flat-file CMS — no database, just text files and JSON on disk. Content is stored in flat files under /bl-content/pages/ and configuration lives in JSON. Depending on which editor you enable (TinyMCE, or the default Markdown processor), your posts will be stored as either HTML or Markdown — but the Nginx configuration is the same either way.
Prerequisites
- Nginx 1.18+
- PHP-FPM 8.1+ (Bludit 3.x requires PHP 7.4+, but 8.1+ is recommended for performance and security)
- A domain name pointed to your server
- SSL certificates — the examples below assume Let's Encrypt via Certbot
Step 1: Download and place Bludit
# Create the web root
sudo mkdir -p /var/www/mysite.com/public_html
# Download Bludit (free version) or grab the Pro zip from Patreon
cd /tmp
wget https://www.bludit.com/releases/bludit-3-22-0.zip
sudo unzip bludit-3-22-0.zip -d /var/www/mysite.com/public_html
# Set ownership — www-data (Debian/Ubuntu) or nginx (RHEL/Fedora)
sudo chown -R www-data:www-data /var/www/mysite.com/public_html
Step 2: Base Nginx server block
Below is the smallest configuration that will get Bludit running. It omits security hardening, static caching, and several production niceties — don't use it as-is on a live site. The full, production-ready configuration is at the end of this article.
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name mysite.com;
# SSL certificates (Let's Encrypt)
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
ssl_certificate /etc/letsencrypt/live/mysite.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mysite.com/privkey.pem;
root /var/www/mysite.com/public_html;
access_log /var/log/nginx/mysite.access.log;
error_log /var/log/nginx/mysite.error.log;
# Bludit's front controller — everything routes through index.php
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
# PHP processing
location ~ \.php$ {
try_files $uri =404;
include fastcgi_params;
fastcgi_pass unix:/var/run/php/php8.5-fpm.sock;
}
}
A few things to note:
try_files $uri $uri/ /index.php$is_args$args;— This is Bludit's entire routing mechanism. Static files and directories are served directly; everything else falls through to Bludit'sindex.phpwith any query string preserved.- No
client_max_body_sizeat the server-block level — uploads belong in the admin area. The Bludit admin uses virtual paths (/admindoesn't exist on disk), so the/adminlocation block in the next section handles body size and routing together. - The
ssl_trusted_certificatedirective is absent — Let's Encrypt phased out OCSP stapling in 2025, and the chain certificate is already bundled infullchain.pem. Onlyssl_certificateandssl_certificate_keyare needed.
The include line pulls in Certbot's recommended TLS protocols, ciphers, and HSTS — all set to Mozilla's intermediate compatibility level by default. ssl_dhparam loads a 4096-bit Diffie-Hellman parameters file that Certbot generates on first install. Both are created automatically when you run certbot --nginx or certbot certonly, and they're safe to share across all your server blocks.
Step 3: Bludit-specific blocks
Bludit stores its content, user data, and workspace files in /bl-content/ and its core in /bl-kernel/. These should never be directly accessible from the web. The admin panel lives at /admin — a virtual path that doesn't exist on disk and needs explicit routing. Save this as bludit.conf and include it from your server block:
# bludit.conf — Bludit security and routing
location ^~ /bl-content/databases/ { deny all; }
location ^~ /bl-content/workspaces/ { deny all; }
location ^~ /bl-content/pages/ { deny all; }
location ~* ^/bl-kernel/.*\.php$ { deny all; }
# Admin area — no caching, body size for uploads, virtual-path routing
location /admin {
expires 0;
client_max_body_size 4M;
# try_files is essential — /admin is a virtual path, not a real directory
try_files $uri $uri/ /index.php$is_args$args;
}
# Expose the sitemap plugin's output at the standard URL
location = /sitemap.xml {
try_files $uri /bl-content/workspaces/sitemap/sitemap.xml =404;
}
The ^~ prefix ensures these location blocks take priority over regex matches (like the PHP handler). bl-content/databases/ contains your users and security keys, bl-content/workspaces/ holds plugin data, and bl-content/pages/ is where your markdown content lives.
The /admin block does three things: expires 0 prevents browsers from caching admin pages, client_max_body_size 4M handles theme and plugin uploads without exposing a server-wide body size, and the try_files directive is essential — /admin is a virtual path Bludit resolves internally through its front controller. Without try_files $uri $uri/ /index.php$is_args$args;, Nginx looks for a directory that doesn't exist and returns a 404. With it, the request reaches Bludit's router and the admin panel loads correctly.
The bl-kernel line uses a regex location (~*) rather than ^~, and the distinction matters. The official Bludit documentation ships with this rule:
# ❌ Does NOT work — carried over from Apache syntax
location ^~ /bl-kernel/*.php { deny all; }
Here's the problem: ^~ creates a prefix location — it matches a literal URI path. Nginx does not interpret the * as a wildcard in prefix matches. So this rule sits waiting for a request to the exact path /bl-kernel/*.php (with an actual asterisk in the URL), which no browser or bot will ever send. Any real PHP file under /bl-kernel/ — say, /bl-kernel/admin.php or /bl-kernel/helpers/text.php — sails right past it, matched instead by the PHP handler.
This syntax was likely carried over from Apache's mod_rewrite, where regex patterns work inside rewrite rules. Nginx prefix locations don't evaluate patterns — they match strings. For pattern matching against the request URI, you need a regex location:
# ✅ Correct — regex location evaluates the pattern against every request URI
location ~* ^/bl-kernel/.*\.php$ { deny all; }
Breaking it down:
~*— Case-insensitive regex match. The request URI is tested against the pattern that follows.^/bl-kernel/— The URI must begin with/bl-kernel/..*— Match zero or more characters in between, covering subdirectories like/bl-kernel/helpers/or/bl-kernel/admin/controllers/.\.php$— A literal dot followed byphpat the end of the string. The backslash escapes the dot so it doesn't match any character.
This blocks direct web access to any .php file anywhere under /bl-kernel/ — at the root, nested in subdirectories, anywhere. The ^~ prefix locations for the bl-content directories remain unchanged because those are correctly matching directories by literal prefix — no wildcards needed.
The sitemap block uses try_files rather than alias — it tries the standard /sitemap.xml path first (in case you drop a static one in the document root), then falls back to the plugin's output inside the protected workspace directory. A =404 at the end ensures a clean 404 instead of an internal redirect loop if neither exists.