Headless CMS
    Git-Backed

    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

    ProjectTinaCMS
    LicenseApache 2.0
    Recommended PlanRamNode Cloud VPS 2 GB+
    OSUbuntu 22.04 / 24.04 LTS
    StackNext.js, MongoDB, Nginx, PM2
    Estimated Setup Time25–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
    1

    Initial Server Setup

    Update system and install tools
    sudo apt update && sudo apt upgrade -y
    sudo apt install -y curl git build-essential
    Set hostname and configure firewall
    sudo hostnamectl set-hostname cms
    sudo ufw allow OpenSSH
    sudo ufw allow 80/tcp
    sudo ufw allow 443/tcp
    sudo ufw enable
    2

    Install Node.js

    Install Node.js 20.x via NodeSource
    curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
    sudo apt install -y nodejs
    Verify and install pnpm
    node -v
    npm -v
    sudo npm install -g pnpm
    3

    Install 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.

    Add MongoDB 7.0 repository
    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.list
    Install and start MongoDB
    sudo apt update
    sudo apt install -y mongodb-org
    sudo systemctl start mongod
    sudo systemctl enable mongod
    Create database user
    mongosh
    
    use admin
    db.createUser({
      user: "tinaadmin",
      pwd: "REPLACE_WITH_A_STRONG_PASSWORD",
      roles: [{ role: "readWrite", db: "tinacms" }]
    })
    exit
    Enable authentication
    # Edit /etc/mongod.conf — update the security section:
    security:
      authorization: enabled
    Restart MongoDB
    sudo systemctl restart mongod
    4

    Create 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.

    5

    Clone and Configure TinaCMS

    Clone the self-hosted starter
    cd /home/$USER
    git clone https://github.com/tinacms/tina-self-hosted-demo.git tinacms
    cd tinacms
    pnpm install
    cp .env.sample .env
    Environment variables
    # .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=false
    Generate NEXTAUTH_SECRET
    openssl rand -base64 32
    6

    Build and Test

    Build and verify
    pnpm build
    pnpm start

    The 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.

    7

    Set Up PM2 Process Management

    Install and configure PM2
    sudo npm install -g pm2
    cd /home/$USER/tinacms
    pm2 start pnpm --name "tinacms" -- start
    pm2 save
    pm2 startup

    Run the command PM2 outputs with sudo to enable auto-start on reboot. Verify with pm2 status.

    8

    Configure Nginx Reverse Proxy

    Install Nginx
    sudo apt install -y nginx
    Nginx server block
    # /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;
        }
    }
    Enable the site
    sudo ln -s /etc/nginx/sites-available/tinacms /etc/nginx/sites-enabled/
    sudo nginx -t
    sudo systemctl restart nginx
    9

    Secure with Let's Encrypt SSL

    Install Certbot and obtain certificate
    sudo apt install -y certbot python3-certbot-nginx
    sudo certbot --nginx -d cms.yourdomain.com
    sudo systemctl status certbot.timer
    Update NEXTAUTH_URL and rebuild
    # Add to .env:
    NEXTAUTH_URL=https://cms.yourdomain.com
    Rebuild and restart
    cd /home/$USER/tinacms
    pnpm build
    pm2 restart tinacms
    10

    Log 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