NANO//CMS github →

OPEN-SOURCE · MIT · v1.2.0

The blog
system your
static site
has been waiting for.

4,500 LOC · 350KB deployed · PHP 8.1+ · MIT

4,500
lines of code
350 KB
deployed
0
database engines
0
js frameworks

// what it is

Markdown files on disk are the database.

Nano CMS solves a specific problem: client websites that should stay as fast static HTML, but need a steady stream of fresh blog content for search ranking. Rather than converting the whole site to WordPress or Joomla, Nano CMS slots a small blog system into an existing static site with minimal disruption.

It is deliberately not a general-purpose CMS. It does one thing and tries to do it well: serve a blog with strong SEO output in as little code as possible. If you've ever installed WordPress just to publish four blog posts a year on a client site, this is for you.

// how it works

Two codebases. One on-disk format.

// 01
permanent

Frontend

Lives inside the client's webroot at /blog/. Renders pages, generates the sitemap and RSS, outputs full SEO metadata on every URL. Once it's there it never needs touching.

// 02
portable / ephemeral

Admin

A universal folder, identical for every site. Upload it via SFTP when you want to publish. Edit posts. Remove the folder when done. No persistent admin = drastically reduced attack surface.

// 03
outside webroot

Config

Password hash and site settings live in a JSON file outside the document root, structurally unreachable via HTTP. Backup is rsync /posts/ /media/ /config.json. That's the entire CMS state.

/public_html/blog/                  // permanent
 ├── posts/      Markdown files (the "database")
 ├── media/      Uploaded images
 ├── core.php    Parser, renderer, ~600 lines
 ├── index.php   Category landing + archives
 ├── post.php    Single post
 └── template.php  Per-site HTML wrapper

/blog-config/                       // OUTSIDE webroot
 ├── config.json     Password hash, site settings
 └── rate-limit.json Login attempt tracking

/public_html/blog/admin/            // ephemeral
 Uploaded via SFTP to publish, removed after.

// features

Everything a small blog needs.
Nothing it doesn't.

Flat-file by design

Markdown posts with YAML frontmatter. No SQLite, no MySQL. Backups are an rsync.

Clean URLs

/blog/<cat>/<slug>/ via .htaccess. No ?p=42 nonsense, ever.

Markdown editor

Toolbar, autocomplete on categories, draft preview behind admin auth. Shortcodes for YouTube and Vimeo.

Category landing

Homepage shows a card grid of categories sorted by post count. Each can have its own hero image.

Auto thumbnails

Every upload generates a pre-cropped thumbnail. Card grids stay light; single-post heroes keep the original.

Per-grid layout

Independent 3- or 4-column settings for the homepage and the article archive. Tune separately from admin.

HTTPS-only admin

HTTPS enforced. CSRF on every POST. bcrypt password hash. Rate-limited login. Sessions invalidated on password change.

Image safety pipeline

Uploads decoded and re-encoded through GD or Imagick. Strips embedded payloads (EXIF smuggling, the works).

Removable admin

Sole-developer-friendly. Upload, publish, remove. Nothing for an attacker to find when you're not actively editing.

// seo output

Technical SEO most WordPress sites need three plugins for.

  • Custom <title> + meta description from frontmatter
  • Canonical URL on every page
  • Open Graph tags (Facebook, LinkedIn)
  • Twitter Card tags
  • JSON-LD BlogPosting schema
  • Semantic HTML5 (<article>, <time>, breadcrumb)
  • loading="lazy" + descriptive alt on every image
  • XML sitemap, regenerated on save
  • RSS 2.0 feed, regenerated on save
  • Clean URLs end-to-end via mod_rewrite

// install

Five minutes,
one setup wizard.

  1. 01
    Upload the frontend zip to /public_html/blog/ on your client's host.
  2. 02
    Edit bootstrap.php with the path to your config directory (sample provided).
  3. 03
    Upload the admin zip to /public_html/blog/admin/.
  4. 04
    Visit https://yoursite.com/blog/admin/setup.php. Set a password. The wizard writes config.json and goes away.
  5. 05
    Publish. Drop the admin folder when done. Your blog stays live.

// backup is one cron line: rsync -az clientsite:/blog/posts/ /backups/clientname/ — the whole CMS state is files.

// comparison

Why not WordPress, Joomla,
or Grav?

Those are excellent for sites that need them. For a static HTML client site that needs occasional SEO content, they aren't.

WordPress

~500K lines

  • Requires a database
  • Ongoing security updates
  • Plugin maintenance
  • Significant attack surface
  • Converts site to a database app

Joomla

~hundreds K lines

  • Steeper learning curve
  • More weight than the use case
  • Same database+app shape

Grav

~30K lines

  • Closest in spirit, standalone builder
  • Own template language and plugins
  • Bigger than client SEO blog use

NANO//CMS

~4.5K lines

  • Drop-in onto existing static sites
  • No database, no plugins, no build
  • Removable admin = minimal surface
  • Static HTML stays static

// who it's for

Web developers, not end users.

Nano CMS assumes operators are fluent with Markdown and comfortable with SFTP. It is not aimed at non-technical end users. For those, WordPress is genuinely the right answer.

If you build static sites for clients and want to add ranking blog content without taking on the weight of a full CMS, this is for you.

Like it? Help keep it going.

Nano CMS is solo-developed and MIT-licensed. If it saved you the cost of a WordPress install on a client site, a coffee covers a lot of late-night debugging.