Self-Host Memos on a VPS for a Personal Microblog
Most note apps want to be a second brain. Memos wants to be a feed. You drop short markdown thoughts into a chronological timeline, tag them, and search later. It feels closer to a private Twitter or Mastodon than to Logseq or Obsidian, which is exactly why it works for the kind of notes you would otherwise lose in Slack DMs to yourself.
Memos is open source, written in Go, ships as a single binary, and the Docker image runs comfortably on a 256 MB VPS. SQLite is the default store, so there's no separate database container to manage. This guide gets you from a fresh server to a working public URL with HTTPS, your own admin account, registrations locked down, and the iOS or Android app pointed at it.
TL;DR
- Install Docker and Docker Compose on a small VPS
- Point a subdomain like
notes.example.comat the server - Run
neosmemo/memos:stablebehind Caddy in onedocker-compose.yml - Sign up as the first user (who becomes Host), then disable open registration
- Install the iOS or Android client and connect to your URL
- Back up the SQLite file in the persistent volume daily
Total time: about 15 minutes.
What You Need
- A VPS with at least 256 MB RAM (512 MB is comfortable) running Ubuntu 22.04 or 24.04
- A domain or subdomain you can point at the server
- Ports
80and443open to the internet for Let's Encrypt - Root or sudo access
Memos is genuinely tiny. On an idle server it sits around 40 MB of RAM with a few hundred notes. If you're already running other Docker workloads, this slots in alongside them.
How Memos Differs from Logseq and Obsidian
Quick orientation, because the question always comes up:
- Logseq and Obsidian are outliner and graph tools. Notes are atomic, linked, and meant to be reorganized. The unit of thought is a page or block.
- Memos is chronological short-form. The unit of thought is a timestamped post. Tags and search replace folder hierarchies. There's no graph, no daily journal scaffolding, no sync-the-vault dance.
Think of Memos as a personal microblog you can also publish from. Many people run all three side by side: Obsidian for long-form, Memos for stream-of-consciousness, and a dedicated bookmark app for links.
Step 1: Point a Subdomain at Your VPS
In your DNS provider, add an A record:
notes.example.com → YOUR_VPS_IPV4
Add an AAAA record if you use IPv6. Verify it resolves:
dig +short notes.example.com
DNS has to point at your VPS before Caddy can fetch a certificate.
Step 2: Install Docker and Docker Compose
On a fresh Ubuntu box:
sudo apt update
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
-o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Confirm:
docker --version
docker compose version
Step 3: Open the Firewall
If you use UFW:
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
Caddy needs port 80 for the ACME HTTP challenge and 443 for HTTPS.
Step 4: Create the Project Directory
sudo mkdir -p /opt/memos
cd /opt/memos
sudo mkdir -p memos-data caddy-data caddy-config
Everything persistent lives under /opt/memos. If you back up this one directory, you can rebuild the stack on any host.
Step 5: Write the Compose File
Memos is a single container. No database service to run, no Redis, no migrations to babysit. Create /opt/memos/docker-compose.yml:
services:
memos:
image: neosmemo/memos:stable
container_name: memos
restart: unless-stopped
volumes:
- ./memos-data:/var/opt/memos
networks:
- memos-net
caddy:
image: caddy:2
container_name: memos-caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./caddy-data:/data
- ./caddy-config:/config
networks:
- memos-net
networks:
memos-net:
A few notes:
- The Memos container exposes port
5230internally. We do not publish it on the host because Caddy reaches it on the Docker network. - The volume mount
./memos-data:/var/opt/memosis the entire data set: SQLite database, uploaded images, and any attached files. neosmemo/memos:stableis the recommended image tag for production. Thelatesttag tracks the dev branch and breaks more often.
Step 6: Write the Caddyfile
Create /opt/memos/Caddyfile:
notes.example.com {
encode zstd gzip
reverse_proxy memos:5230
}
Caddy will request a Let's Encrypt certificate for notes.example.com automatically on first boot. No extra config needed.
Step 7: Start the Stack
cd /opt/memos
sudo docker compose up -d
sudo docker compose logs -f
Watch the logs until Caddy reports the certificate was issued. Then open https://notes.example.com in a browser. You should see the Memos sign-up screen.
Step 8: Create the Host Account
The first account you create on a fresh Memos install is automatically promoted to Host, which is the equivalent of root admin. Sign up with the username and password you want to keep.
Once you're in, you'll see the timeline view, an empty editor, and the side rail with tags and explore tabs. The keyboard shortcut to post is Ctrl+Enter (or Cmd+Enter on macOS).
Step 9: Disable Public Registration
In the Memos web UI:
- Click your avatar in the bottom-left, then Settings.
- Open the Workspace tab (only visible to Host accounts).
- Find Disallow user registration (or Disable signup, depending on version) and toggle it on.
- Save.
To add other users later, the Host account can create them manually from the same Workspace settings panel under Members.
While you're in Workspace settings, also confirm:
- Disable password login stays off unless you have an SSO provider set up.
- Visibility default is set to
Private, so new memos don't accidentally land on a public timeline.
Step 10: Install the Mobile App
Memos has community iOS and Android clients that talk to your server over the same REST API the web UI uses.
- iOS: the Memos app on the App Store. On first launch, tap Sign In, enter
https://notes.example.comas the host, then your username and password. - Android: Moememos on Google Play or F-Droid. Same flow: server URL plus credentials.
For both apps, the URL must be the full https://... form. If the client refuses to log in, double-check that your domain is reachable in a browser first. The phone never sees a more lenient version of TLS than your laptop does.
Step 11: Tagging and Search Basics
Memos uses inline hashtags, like a microblog. Three things worth knowing on day one:
#taganywhere in a memo creates that tag automatically. The sidebar lists tags by frequency.- Nested tags work with
/, for example#books/2026and#books/wishlist. The sidebar groups them in a tree. - The search bar across the top accepts plain keywords, tag filters (
tag:books), and date filters (from:2026-04-01 to:2026-04-30).
Visibility per memo is set with the dropdown next to the post button:
- Private: only you can see it.
- Workspace: other users on your instance can see it.
- Public: anyone with the direct link can see it, and it shows up on the public Explore page if that's enabled.
A handy pattern is to tag work-in-progress items #inbox, then sweep the inbox view once a day and either delete, retag, or expand them.
Step 12: Back Up the Volume
The SQLite file in /opt/memos/memos-data is the single source of truth. Lose it and you've lost everything.
Create /usr/local/bin/memos-backup.sh:
#!/usr/bin/env bash
set -euo pipefail
BACKUP_DIR="/var/backups/memos"
DATE="$(date +%F-%H%M)"
mkdir -p "$BACKUP_DIR"
docker exec memos sqlite3 /var/opt/memos/memos_prod.db ".backup '/var/opt/memos/memos.backup.db'"
tar -czf "$BACKUP_DIR/memos-$DATE.tar.gz" -C /opt/memos memos-data
find "$BACKUP_DIR" -name "memos-*.tar.gz" -mtime +14 -delete
The sqlite3 .backup command produces a consistent snapshot even while Memos is running, which is safer than tarring the live database file.
Make it executable and schedule a daily run:
sudo chmod +x /usr/local/bin/memos-backup.sh
echo "20 3 * * * root /usr/local/bin/memos-backup.sh" | \
sudo tee /etc/cron.d/memos-backup
For off-site safety, sync /var/backups/memos to S3, Backblaze B2, or another VPS using rclone on the same schedule.
To restore: stop the stack, replace memos-data/ with the contents of a backup, start the stack again.
Step 13: Upgrade Memos Safely
Memos ships frequent point releases. The safe upgrade path:
cd /opt/memos
sudo docker compose pull
sudo docker compose up -d
Always take a fresh backup first. Across major releases the schema occasionally changes, and rolling back is much easier with a known-good snapshot than with a half-migrated database.
Optional: Put Memos Behind a VPN
A personal microblog that no one else needs to read is a great candidate for VPN-only access. Pair this with our Tailscale guide so notes.example.com only resolves and answers inside your tailnet. The mobile clients work fine over WireGuard or Tailscale.
Troubleshooting
Caddy returns 502 Bad Gateway. The Memos container hasn't fully started, or it crashed on boot. Check sudo docker compose logs memos. Usually a permissions issue on the volume - run sudo chown -R 1000:1000 /opt/memos/memos-data and restart.
Login loop after upgrading Caddy. The browser cookie was set with Secure over a connection Caddy now serves slightly differently. Hard-refresh, then clear cookies for notes.example.com. If it persists, check that the Caddyfile still has only the apex notes.example.com { block and isn't redirecting to a different host.
Image uploads fail with permission errors. Memos writes uploads under /var/opt/memos/assets. Inside the container that path needs to be writable by UID 1000. Same fix as above: sudo chown -R 1000:1000 /opt/memos/memos-data.
API key returns 401 Unauthorized. Memos API tokens are bound to a specific user. If you regenerated one in the UI, the old value is dead immediately. Send the token as a Bearer header: Authorization: Bearer YOUR_TOKEN. Some scripts mistakenly send it as Token YOUR_TOKEN, which the server rejects.
Caddy fails to issue a certificate. DNS hasn't propagated, or 80/443 are blocked by your provider. Verify with dig +short notes.example.com and curl -I http://notes.example.com.
Going Further
- Automate posts with the REST API. Memos exposes a documented API at
/api/v1/.... Generate a token inSettings → My Account → Access Tokens, then post a memo from anywhere withcurl -H "Authorization: Bearer $TOKEN" -d '{"content":"hello"}' https://notes.example.com/api/v1/memos. Great for shortcuts on iOS, browser bookmarklets, or shell aliases. - Subscribe to your own RSS feed. Public memos are exposed at
https://notes.example.com/u/USERNAME/rss.xml. Plug it into any reader to get a chronological feed of your public posts, or use it to syndicate to a static site. - Mobile clients beyond the official one. Moememos on iOS and Android both support multiple accounts, drafts, and image uploads, and they pair well with iOS Shortcuts.
- Add Linkding for bookmarks. Memos is great at thoughts, less great at storing dozens of URLs. Linkding is the bookmark equivalent: same single-container philosophy, same Caddy reverse-proxy pattern, separate subdomain. The two complement each other nicely.
Self-hosted Memos is the kind of tool you forget about for weeks because it just runs. A few hundred notes in, the timeline plus tag search becomes the easiest place to write down anything that doesn't deserve a full markdown page.
Looking for a small VPS that fits a stack like this without breaking a sweat? Our Linux plans include fast NVMe storage, IPv6, and plenty of headroom for Docker workloads. See the options.