Deploy TinaCMS on a VPS
Open-source, Git-backed headless CMS with real-time visual editing — self-hosted with MongoDB, Next.js, Nginx, and PM2.
At a Glance
| Project | TinaCMS |
| License | Apache 2.0 |
| Recommended Plan | RamNode Cloud VPS 2 GB+ |
| OS | Ubuntu 22.04 / 24.04 LTS |
| Stack | Next.js, MongoDB, Nginx, PM2 |
| Estimated Setup Time | 25–35 minutes |
Prerequisites
- A RamNode VPS with at least 2 GB RAM
- Ubuntu 22.04 or 24.04 LTS
- A registered domain name with DNS pointed to your VPS
- A GitHub account and repository for content storage
- SSH access with a sudo-enabled user
Initial Server Setup
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl git build-essentialsudo hostnamectl set-hostname cms
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enableInstall Node.js
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejsnode -v
npm -v
sudo npm install -g pnpmInstall and Configure MongoDB
TinaCMS uses MongoDB as a cache layer for its GraphQL API. Content source of truth remains your Git-backed Markdown and JSON files.
curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc | sudo gpg -o /usr/share/keyrings/mongodb-server-7.0.gpg --dearmor
echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.listsudo apt update
sudo apt install -y mongodb-org
sudo systemctl start mongod
sudo systemctl enable mongodmongosh
use admin
db.createUser({
user: "tinaadmin",
pwd: "REPLACE_WITH_A_STRONG_PASSWORD",
roles: [{ role: "readWrite", db: "tinacms" }]
})
exit# Edit /etc/mongod.conf — update the security section:
security:
authorization: enabledsudo systemctl restart mongodCreate a GitHub Personal Access Token
Navigate to GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic) and generate a new token with the repo scope. Copy the token immediately — GitHub will not show it again.
Clone and Configure TinaCMS
cd /home/$USER
git clone https://github.com/tinacms/tina-self-hosted-demo.git tinacms
cd tinacms
pnpm install
cp .env.sample .env# .env
GITHUB_OWNER=your-github-username
GITHUB_REPO=your-repo-name
GITHUB_BRANCH=main
GITHUB_PERSONAL_ACCESS_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx
MONGODB_URI=mongodb://tinaadmin:REPLACE_WITH_A_STRONG_PASSWORD@127.0.0.1:27017/tinacms?authSource=admin
NEXTAUTH_SECRET=run-openssl-rand-base64-32-to-generate-this
TINA_PUBLIC_IS_LOCAL=falseopenssl rand -base64 32Build and Test
pnpm build
pnpm startThe app should be accessible on port 3000. Navigate to http://YOUR_VPS_IP:3000/admin/index.html to verify the admin interface loads. Press Ctrl+C to stop.
Set Up PM2 Process Management
sudo npm install -g pm2
cd /home/$USER/tinacms
pm2 start pnpm --name "tinacms" -- start
pm2 save
pm2 startupRun the command PM2 outputs with sudo to enable auto-start on reboot. Verify with pm2 status.
Configure Nginx Reverse Proxy
sudo apt install -y nginx# /etc/nginx/sites-available/tinacms
server {
listen 80;
server_name cms.yourdomain.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}sudo ln -s /etc/nginx/sites-available/tinacms /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginxSecure with Let's Encrypt SSL
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d cms.yourdomain.com
sudo systemctl status certbot.timer# Add to .env:
NEXTAUTH_URL=https://cms.yourdomain.comcd /home/$USER/tinacms
pnpm build
pm2 restart tinacmsLog In and Update Default Password
Navigate to https://cms.yourdomain.com/admin/index.html. Default credentials are in content/users/index.json. Update the password immediately after first login.
Maintenance
- Update TinaCMS:
cd ~/tinacms && git pull && pnpm install && pnpm build && pm2 restart tinacms - View logs:
pm2 logs tinacms - Nginx logs:
sudo tail -f /var/log/nginx/error.log - Backup MongoDB:
mongodump --uri="mongodb://tinaadmin:PASSWORD@127.0.0.1:27017/tinacms?authSource=admin" --out=/home/$USER/backups/mongo/$(date +%Y%m%d)
Architecture Overview
- Next.js serves frontend and TinaCMS backend API (GraphQL + auth)
- MongoDB acts as cache layer for search, filtering, and pagination
- GitHub stores content files (Markdown, MDX, JSON) as source of truth
- Auth.js handles user authentication
- Nginx reverse proxy with SSL termination
- PM2 manages the Node.js process with auto-restart
