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_IP
If you encounter connection issues due to known_hosts
errors, apply these permissions:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/known_hosts
Establish a proper hostname for system identification:
sudo hostnamectl set-hostname content.yourdomain.com
Next, update your hosts file to reflect this configuration:
sudo nano /etc/hosts
Add the following line:
1YOUR_SERVER_IP content.yourdomain.com
In 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 ufw
Strapi 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-contrib
Create a dedicated database and user for your Strapi application:
sudo -i -u postgres
psql
Inside 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
5exit
Security Note: Generate a strong, unique password for your database user. Consider using
openssl rand -hex 16
to 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 nodejs
Verify the installation with:
node -v # Should show v18.x.x or higher
npm -v # Should show 8.x.x or higher
With 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-production
Alternatively, you can transfer your local project via SCP or SFTP.
Install required packages and production dependencies:
npm install
npm install pg dotenv --save
Create a dedicated production environment configuration:
mkdir -p config/env/production
touch config/env/production/.env
Edit 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=false
For the various secret keys, generate random strings:
openssl rand -hex 32 # Run this multiple times for each key
Create a database configuration file specific to production:
nano config/env/production/database.ts
Add 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 build
This 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 pm2
pm2 start npm --name "strapi-production" -- run start
pm2 save
pm2 startup systemd
Follow 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 nginx
Create a new Nginx configuration file:
sudo nano /etc/nginx/sites-available/strapi
Add 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 nginx
Secure your application with a free SSL certificate:
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d content.yourdomain.com
Follow 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 enable
Verify the firewall status:
sudo ufw status
If 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_TOKEN
If automatic transfer isn't available:
Export from source:
pg_dump -h SOURCE_HOST -U SOURCE_USER -d SOURCE_DB -F c -f backup.dump
Transfer 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.dump
For 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 save
Enhance Nginx configuration for static asset caching:
sudo nano /etc/nginx/sites-available/strapi
Add 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 nginx
Establish a robust maintenance routine for long-term stability:
Set up automated daily database backups:
mkdir -p ~/backups
crontab -e
Add:
10 0 * * * pg_dump -U strapiuser -d strapidb -F c -f ~/backups/strapi-backup-$(date +\%Y\%m\%d).dump
When updating your Strapi application:
Pull latest changes:
cd ~/strapi-production
git pull origin main
Install dependencies:
npm install
Rebuild and restart:
NODE_ENV=production npm run build
pm2 restart strapi-production
Implement basic monitoring with PM2:
pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 7
Following 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