Quick start #
- Create a folder with a
_pulpmesh.mdfile and at least one post, for examplehello-world.md. - Upload the folder, replacing
NAMEwith the name you want:scp -r ./notes NAME@pulpme.sh:/ - Open
https://pulpme.sh/~NAME.
The SSH key used for the first upload becomes the only key that can publish under that name. See Names.
Names #
The SSH username is the blog name. There is no sign-up step.
Claiming a name
The first connection to a name that nobody holds claims it and records the
public key that connected. This applies to any connection, including a plain
ssh NAME@pulpme.sh, so check the spelling before you connect.
After that, only the recorded key is accepted for the name. A different key
gets Permission denied (publickey).
Name rules
- 1 to 32 characters.
- Lowercase letters
a-z, digits0-9and hyphens. - Must start and end with a letter or a digit.
The following names are reserved and cannot be claimed:
about, admin, api,
assets, cdn, docs, ftp,
help, mail, pulpme,
pulpmesh, root, ssh,
static, status, support,
www. Connecting with a reserved or invalid name prints the
reason and disconnects.
Publishing #
Uploading
Upload the folder recursively to /:
Both the SFTP-based scp of OpenSSH 9.0 and later and the
legacy protocol (scp -O) are supported, as is
sftp. If the upload contains a single top-level folder, its
contents are used as the blog. Uploading ./notes and uploading
the files inside it give the same result.
The server only accepts uploads. Listing, downloading, renaming and deleting files are not supported.
Replacement
Each upload replaces the blog's source completely. Files that are not in the upload are removed from the site. Always upload the full folder, not only the files you changed.
The new version goes live only after the upload finishes and the build succeeds. If the upload is interrupted, a file is rejected, or the build fails, the previous version stays online unchanged.
If the uploaded files are identical to the published ones, no build runs.
Checking the last build
This prints the date the name was claimed, the blog's address, and the
result of the last build with any warnings from the renderer. Use it when a
publish does not appear: scp closes the connection before it can
display a build error.
Messages
| Message | Cause |
|---|---|
Permission denied (publickey) | The name is held by a different key. |
… is a name this service keeps for itself | The name is reserved. |
… can't be a blog name | The name breaks the name rules. |
… is not a Markdown file | The upload contains a file other than .md or _styles.css. |
… over the 33 KiB a single file can be | A file exceeds the per-file limit. |
too many files | The upload has more than 500 files. |
too many publishes in a row | The build rate limit was reached. Wait and upload again. |
your files match what is already published | Nothing changed, so no build ran. |
your blog didn't build | The renderer failed. The output that follows gives the reason. |
only file uploads are supported | The connection ran a command or file operation other than an upload. |
Files #
| File | Purpose |
|---|---|
_pulpmesh.md | Blog settings and index page intro. Optional. See Blog settings. |
*.md | A post. |
_*.md | A draft. Stored with the upload, never rendered. |
_styles.css | Custom stylesheet. Optional. See Styles. |
Any other file type is rejected and the upload stops at that file.
Only the top level of the folder is read. Files in subdirectories are accepted but never published.
Blog settings #
The front matter of _pulpmesh.md holds the blog settings. The
body after the front matter is rendered as the intro at the top of the index
page. All keys are optional, and a blog without _pulpmesh.md
still builds.
Keys
| Key | Description | Default |
|---|---|---|
title | Blog name, shown in the header, the page title, and the list on the pulpme.sh front page. | src |
description | Line shown under the title. Also used as the meta description. | none |
keywords | Meta keywords. | blog, blogging, write, writing, hackers, developers, terminal |
footer | One line of Markdown shown at the bottom of every page. | pulpmesh credit line |
favicon | URL of the site icon. Must point to another host, because images cannot be uploaded. | renderer's icon |
card_image | Absolute URL of a 300×300 image shown when the blog is linked on social sites. | none |
with_styles | Set to false to build without _styles.css. | true |
Set title. Without it the blog header shows
src.
Example
---
title: Ada's notebook
description: reading notes, mostly
footer: "[mastodon](https://example.com/@ada)"
---
Notes on what I read, posted when
I get around to it.
Front page listing
The pulpme.sh front page lists recently published blogs by title. The title
is taken from title, or from the first heading in the body of
_pulpmesh.md if title is not set. The blog's own
header only uses title.
RSS feed
Every build writes a feed to pulpme.sh/~NAME/rss.xml. No
setting is required.
Posts #
Front matter
| Key | Format | Description |
|---|---|---|
date | 2026-06-20 | Publish date. Values in any other format are ignored. |
title | text | Post title. Used only when the post has no # heading. |
description | text | Meta description, quoted by social sites. Not shown on the page. |
tags | [ruby, static-site] | Shown at the end of the post and in the feed. Tags are not links. |
All keys are optional.
Example
---
date: 2026-06-20
tags: [ruby, static-site]
---
# Frontier model
Body text.
Title
The title is taken from the first source that exists:
- The first
#heading. It is removed from the body so it is not shown twice. - The
titlekey. - The filename.
Date
The date is taken from the first source that exists:
- The
datekey. - A
YYYY-MM-DD-prefix on the filename, as in2026-06-20-frontier-model.md. - The time of the upload.
scp -p. A post without a date key or
a date prefix is re-dated on every upload. The "last updated" line on each
post always shows the date of the most recent upload.URLs
A post is published at pulpme.sh/~NAME/ followed by its
filename with .html in place of .md.
| File | URL path |
|---|---|
hello-world.md | hello-world.html |
0010-frontier-model.md | frontier-model.html |
2026-06-20-frontier-model.md | frontier-model.html |
my first post.md | my-first-post.html |
_half-baked.md | not published (draft) |
- A numeric prefix such as
0010-or a date prefix such as2026-06-20-is removed. Use it to order files locally. - Characters that need escaping in a URL are replaced with
-. Non-ASCII letters are kept. - If two files map to the same URL, the build fails and the previous version stays online.
Markdown #
Supported syntax
| Feature | Supported | Notes |
|---|---|---|
| Headings | yes | # Title or a line underlined with ===. |
| Lists | yes | Tight lists only. A blank line ends the list. |
| Fenced code | yes | See Syntax highlighting. |
| Inline links | yes | See Links. |
| Images | yes | See Images. |
| Line breaks | yes | A newline inside a paragraph is a line break. |
| Reference links | no | Rendered as plain paragraph text. |
| Tables | no | Rendered as plain paragraph text. |
| Footnotes | no | Rendered as plain paragraph text. |
| Raw HTML | no | Escaped and shown as text. |
HTML and entities
HTML is not interpreted. <b>bold</b> and
<script> are shown on the page as typed. Entity references
such as © are also shown as typed, so write the
character itself.
Links
Allowed link destinations are http:, https:,
mailto:, relative paths, and anchors. A link with any other
scheme is not created, and the source text is shown instead.
Typography
| Input | Output |
|---|---|
"text", 'text' | “text”, ‘text’ |
-- | – (en dash) |
--- | — (em dash) |
... | … (ellipsis) |
These replacements are not applied inside code, URLs, or image alt text.
Syntax highlighting
| Language | Fence tags |
|---|---|
| Shell | sh, bash, shell, zsh, console |
| Ruby | ruby, rb |
Code blocks with any other tag, or none, are rendered as plain text without a warning.
Styles #
Custom CSS
Every page links three stylesheets in this order:
- The base styles.
- The syntax highlighting theme.
- Your
_styles.css, if present.
Because _styles.css is loaded last, its rules take precedence.
To keep the file in the upload but build without it, set
with_styles: false in _pulpmesh.md.
Images
Images cannot be uploaded.  only displays
if the path resolves to an image hosted elsewhere, so use an absolute URL.
Limits #
| Limit | Value |
|---|---|
| File size | 33 KiB per file |
| Upload size | 25 MiB per upload |
| File count | 500 files per upload |
| Build rate | 3 builds in a row, then one every 10 seconds |
| Build time | 10 seconds |
An upload that exceeds a size or count limit stops at the file that exceeded it, and the error names that file. A build that exceeds the time limit fails. In both cases the previous version stays online.
Uploads with no changes do not count toward the build rate.