In today's digital landscape, content management systems (CMS) form the backbone of most modern websites and applications. While cloud-hosted solutions offer convenience, self-hosting on a Virtual Private Server (VPS) provides unparalleled control, security, and cost-effectiveness for growing businesses.
Strapi 5, the latest iteration of the popular open-source headless CMS, delivers exceptional flexibility for developers while maintaining an intuitive interface for content editors. When deployed correctly on a VPS, it creates a powerful foundation for scalable content-driven experiences across web, mobile, and IoT platforms.
This comprehensive guide walks technical teams through establishing a production-ready Strapi 5 instance on a VPS in under 60 minutes. Whether you're migrating from a cloud-hosted solution or building from scratch, these battle-tested steps ensure a secure, optimized deployment that maximizes performance while minimizing operational costs.
Key Benefit: Self-hosting Strapi 5 on a VPS typically reduces monthly hosting costs by 40-70% compared to equivalent managed services while providing complete control over your infrastructure and data.
Before diving into the deployment process, ensure you have:
npx create-strapi-app@latest my-project)Pro Tip: While 2GB RAM is the minimum recommendation, allocating 4GB significantly improves build performance when working with complex content types or numerous API endpoints.
Your VPS security forms the foundation of your Strapi deployment. Let's establish a robust environment before installing any application components.
Connect to your newly provisioned server using:
ssh username@YOUR_SERVER_IPIf you encounter connection issues due to known_hosts errors, apply these permissions:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/known_hostsEstablish a proper hostname for system identification:
sudo hostnamectl set-hostname content.yourdomain.comNext, update your hosts file to reflect this configuration:
sudo nano /etc/hostsAdd the following line:
1YOUR_SERVER_IP content.yourdomain.comIn your domain registrar's DNS settings, create an A record that points content.yourdomain.com to your server's IP address. This configuration typically propagates within 30 minutes to 24 hours, depending on your DNS provider.
Ensure your system packages are current to patch potential security vulnerabilities:
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl git unzip ufwStrapi 5 supports multiple database systems, but PostgreSQL offers the best combination of performance, reliability, and feature support for production deployments.
Install PostgreSQL server and client tools:
sudo apt install -y postgresql postgresql-contribCreate a dedicated database and user for your Strapi application:
sudo -i -u postgres
psqlInside the PostgreSQL console, execute:
1CREATE DATABASE strapidb;
2CREATE USER strapiuser WITH ENCRYPTED PASSWORD 'YOUR_SECURE_PASSWORD';
3GRANT ALL PRIVILEGES ON DATABASE strapidb TO strapiuser;
4\q
5exitSecurity Note: Generate a strong, unique password for your database user. Consider using
openssl rand -hex 16to create a cryptographically secure random string.
Strapi 5 requires Node.js 18 or higher. Let's install the LTS version for optimal stability.
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt install -y nodejsVerify the installation with:
node -v # Should show v18.x.x or higher
npm -v # Should show 8.x.x or higherWith our environment prepared, we can now deploy the Strapi application itself.
If your Strapi project is hosted in a Git repository (recommended for team environments):
cd ~
git clone https://github.com/YOUR_USERNAME/YOUR_REPO.git strapi-production
cd strapi-productionAlternatively, you can transfer your local project via SCP or SFTP.
Install required packages and production dependencies:
npm install
npm install pg dotenv --saveCreate a dedicated production environment configuration:
mkdir -p config/env/production
touch config/env/production/.envEdit this file with your preferred text editor and add:
1HOST=0.0.0.0
2PORT=1337
3APP_KEYS=key1,key2,key3,key4
4API_TOKEN_SALT=YOUR_TOKEN_SALT
5ADMIN_JWT_SECRET=YOUR_ADMIN_JWT
6JWT_SECRET=YOUR_JWT_SECRET
7TRANSFER_TOKEN_SALT=YOUR_TRANSFER_SALT
8
9# Database
10DATABASE_CLIENT=postgres
11DATABASE_HOST=localhost
12DATABASE_PORT=5432
13DATABASE_NAME=strapidb
14DATABASE_USERNAME=strapiuser
15DATABASE_PASSWORD=YOUR_SECURE_PASSWORD
16DATABASE_SSL=falseFor the various secret keys, generate random strings:
openssl rand -hex 32 # Run this multiple times for each keyCreate a database configuration file specific to production:
nano config/env/production/database.tsAdd the following configuration:
1export default ({ env }) => ({
2 connection: {
3 client: env('DATABASE_CLIENT', 'postgres'),
4 connection: {
5 host: env('DATABASE_HOST', 'localhost'),
6 port: env.int('DATABASE_PORT', 5432),
7 database: env('DATABASE_NAME', 'strapidb'),
8 user: env('DATABASE_USERNAME', 'strapiuser'),
9 password: env('DATABASE_PASSWORD'),
10 ssl: env.bool('DATABASE_SSL', false),
11 },
12 debug: false,
13 },
14});Build your Strapi application for production deployment:
NODE_ENV=production npm run buildThis process may take several minutes, especially on VPS instances with limited resources.
To ensure your Strapi application runs continuously and restarts automatically after server reboots, we'll implement PM2.
npm install -g pm2pm2 start npm --name "strapi-production" -- run startpm2 save
pm2 startup systemdFollow the instructions provided by the pm2 startup command to enable automatic startup.
To expose your Strapi instance securely to the internet, we'll use Nginx as a reverse proxy with SSL encryption.
sudo apt install -y nginxCreate a new Nginx configuration file:
sudo nano /etc/nginx/sites-available/strapiAdd the following configuration:
1server {
2 server_name content.yourdomain.com;
3
4 location / {
5 proxy_pass http://localhost:1337;
6 proxy_http_version 1.1;
7 proxy_set_header X-Forwarded-Host $host;
8 proxy_set_header X-Forwarded-Server $host;
9 proxy_set_header X-Real-IP $remote_addr;
10 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
11 proxy_set_header X-Forwarded-Proto $scheme;
12 proxy_set_header Host $http_host;
13 proxy_set_header Upgrade $http_upgrade;
14 proxy_set_header Connection "Upgrade";
15 proxy_pass_request_headers on;
16 }
17}Enable this configuration:
sudo ln -s /etc/nginx/sites-available/strapi /etc/nginx/sites-enabled/
sudo nginx -t # Test configuration
sudo systemctl restart nginxSecure your application with a free SSL certificate:
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d content.yourdomain.comFollow the prompts to complete the SSL configuration.
Implement a basic firewall to protect your server:
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enableVerify the firewall status:
sudo ufw statusIf you're migrating from Strapi Cloud or another hosting provider, you'll need to transfer your data.
For automated migration from another Strapi instance:
npx strapi transfer --from=remote --url=https://YOUR_EXISTING_STRAPI/admin --token=YOUR_TRANSFER_TOKENIf automatic transfer isn't available:
Export from source:
pg_dump -h SOURCE_HOST -U SOURCE_USER -d SOURCE_DB -F c -f backup.dumpTransfer to your VPS:
scp backup.dump username@YOUR_SERVER_IP:/home/username/Restore on your VPS:
pg_restore -U strapiuser -d strapidb -F c backup.dumpFor transferring uploads and media files:
rsync -avz -e ssh /path/to/local/uploads/ username@YOUR_SERVER_IP:/home/username/strapi-production/public/uploads/Fine-tune your Strapi deployment for optimal performance:
Edit your PM2 configuration:
pm2 stop strapi-production
pm2 start npm --name "strapi-production" --node-args="--max_old_space_size=2048" -- run start
pm2 saveEnhance Nginx configuration for static asset caching:
sudo nano /etc/nginx/sites-available/strapiAdd within the server block:
1location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
2 proxy_pass http://localhost:1337;
3 expires 30d;
4 add_header Cache-Control "public, no-transform";
5}Restart Nginx:
sudo systemctl restart nginxEstablish a robust maintenance routine for long-term stability:
Set up automated daily database backups:
mkdir -p ~/backups
crontab -eAdd:
10 0 * * * pg_dump -U strapiuser -d strapidb -F c -f ~/backups/strapi-backup-$(date +\%Y\%m\%d).dumpWhen updating your Strapi application:
Pull latest changes:
cd ~/strapi-production
git pull origin mainInstall dependencies:
npm installRebuild and restart:
NODE_ENV=production npm run build
pm2 restart strapi-productionImplement basic monitoring with PM2:
pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 7Following this guide, you've successfully deployed a production-ready Strapi 5 instance on your VPS with:
Your Strapi CMS is now ready to power your content-driven digital experiences with maximum flexibility and control at a fraction of the cost of cloud-hosted alternatives.
Did you know? Self-hosting Strapi can reduce your total cost of ownership by up to 65% compared to equivalent managed SaaS alternatives over a two-year period.
At Ideaflow Studio, we specialize in crafting exceptional digital experiences powered by modern headless CMS architectures. Our team of certified Strapi experts can help you:
Contact us today at hello@ideaflow.studio to discuss how we can help accelerate your digital transformation with Strapi.
*[VPS]: Virtual Private Server *[CMS]: Content Management System