← All case studies Medium → Ghost

Carlo Mencarelli moved 18 Medium posts to self-hosted Ghost

He pasted Medium's HTML export into Ghost on AWS Lightsail, rehosted the images, and flipped DNS after testing one Cloudflare bulk redirect.

Carlo Mencarelli moved carlo.cloud off Medium onto self-hosted Ghost. He published the write-up on 6 February 2024. This is his own post, not a vendor page.

He had 18 posts to move. He pasted Medium’s HTML export into Ghost, rehosted the images, then pointed DNS at a new Lightsail box.

If you are looking at the same route, start with the Medium → Ghost playbook. Carlo’s post is the field report for a small custom-domain site. The playbook is the export, image, URL, and verification path.

Why he left

He had been on Medium a little over two years. The first posts went into Medium’s editor. That wore off. The in-browser editor gave him basic formatting, dividers, and images. He started writing elsewhere and pasting the finished piece in.

The network still helped. Medium shows stories in the homepage feed, Digest mail, and “Recommended for you” blocks. He liked the occasional extra traffic.

The rest of the platform bothered him. Medium pushed member-only stories and kept changing how writers get paid. He never joined that. None of his posts sat behind the paywall. He still saw clickbait and later GenAI pieces recommended next to his work.

He wrote that Medium announced an end of support for custom domains sometime in 2023. I could not find that announcement. Medium’s help still documents custom domains as a member feature. You point A records at Medium’s IPs. Proxies are not allowed.

After that year the domain started acting up. Sometimes carlo.cloud sent him to medium.com. Sometimes it sent him to his Medium profile, or to an error page. It was not constant. He could not prove it was the custom-domain setup. He treated the feature as leftover, so he did not expect anyone to keep fixing it.

Why Ghost

He looked at WordPress, Jekyll, and 11ty. He already runs Jekyll on another site, so a static generator was not the scary part.

Writing every post in Markdown felt like a reason to skip writing. Turning Medium HTML into Markdown looked harder than it first seemed. That is why he waited.

Ghost felt closer to Medium. The editor looked familiar, and he could theme it. Ghost is open source. The same product runs on Ghost(Pro) or on your own box.

He could have used Ghost(Pro). He could have self-hosted on a generic VPS. He picked Amazon Lightsail as a middle option. Lightsail ships a Ghost blueprint. He said he was not sure he would stay on it. I did not find the Lightsail follow-up he promised.

One Ghost limit annoyed him. Built-in newsletters need Mailgun. He was already on AWS and wanted SES. Ghost says Mailgun is the only supported bulk-mail API for that feature. SMTP is for transactional mail. If you skip Ghost’s newsletter, you do not need Mailgun at all.

How he moved it

He delayed because he thought he had to convert Medium HTML to Markdown.

Then he found Medium’s export. It lives under Settings → Security and apps. You click Download your information. Medium emails a zip of HTML files.

Ghost now has a Medium migrator in Settings → Advanced → Import/Export. You upload that zip. In February 2024 he did not use it. He opened each HTML file, copied the body, and pasted it into a new Ghost post.

He said the formatting came across well. Ghost’s editor accepts pasted markup and can hold leftover HTML in a card. I am not inventing which card he used. He only says paste.

Images were a separate job. The export still pointed at Medium. He did not want to hotlink those files. He rehosted them on Ghost. The playbook calls this out: remote media disappears once the source is gone.

DNS was the risky part. Old A records still pointed at Medium. The new Ghost box already had a public IP and the 18 posts. He did not want an old Medium link to 404 on Ghost.

He described Medium slugs as a title fragment plus a hash. Ghost’s docs match that. Medium appends a short hex id to each post, then the official importer strips it. Ghost’s redirect tutorial has the regex that drops that suffix:

301:
  ^\/(?!p\/)(.*)(-[0-9a-f]{10,12}): /$1

That rule turns /some-post-a1b2c3d4e5f6 into /some-post. He had 18 posts. He skipped Ghost’s redirects.yaml and used Cloudflare Bulk Redirects instead. That is a static source-to-target list. Cloudflare only applies those hops when the hostname is proxied.

He tested one low-traffic post first. When that hop worked, he switched the A record from Medium to Ghost. He said Cloudflare’s DNS change was fast. He still treated DNS as the weak link.

What he gained

He picked the host and the theme. The writing no longer lives on Medium’s layout.

He still planned to cross-post to Dev.to and Community.AWS. carlo.cloud is the home copy.

He said he felt better about where the writing lives and how his audience sees it. That is his read, not a metric. He did not publish a Lighthouse score or a hosting bill, so I am not inventing either.

The Ghost site is still up. The homepage I fetched in October 2026 still has new posts.

What broke or got skipped

Images did not travel with the paste. He had to replace every Medium image URL.

Old Medium URLs do not match Ghost slugs. Without a redirect list, those 18 links 404. The cutover work was that URL map.

He left Medium’s distribution. Followers, claps, and the recommendation feed stay on Medium. The playbook marks those as lost. He does not describe exporting them.

Ghost’s official importer also sucks in comments you wrote on Medium. It parks likely comments as drafts tagged #Medium Possible Comment. He pasted posts by hand, so he skipped that mess. He also skipped a subscriber import. He does not mention members.

Mailgun stayed a gap. If he wanted Ghost to send the newsletter, SES was not the built-in path.

He does not describe a failed launch, a 404 after the A-record flip, or a rollback. The problems he names are the ones he planned around.

What I would copy

Export the Medium zip before you touch Ghost. The files are HTML. Keep that zip as the restore copy.

Skip the Markdown conversion if Ghost is the destination. Paste the HTML, or use Ghost’s Medium migrator if you want the zip imported in one shot. Then check images. Rehost every file that still points at Medium.

Write the URL list next. Every Medium path with its hash. Every Ghost slug. One 301 each, or Ghost’s regex if the slugs stay the same after the id is stripped. Eighteen rows fit Cloudflare Bulk Redirects. A bigger archive still wants that list before DNS moves.

Test one quiet URL first. Then flip the A record. Carlo already had content on the new IP. Build the Ghost site first, then change DNS.

If you wanted WordPress instead, that path is the Medium → WordPress playbook. He looked at WordPress and picked Ghost.

Run the Medium → Ghost playbook. Use Carlo’s post for the HTML-paste and one-URL-then-DNS path.