Introduction
Strapi lets you:
- Define content types via web UI (Content-Type Builder)
- Access data via auto-generated REST + GraphQL
- Manage permissions (roles, plugins)
- Plugin marketplace (i18n, email, S3 upload)
- Webhooks, locales, drafts/published
Typical stack: Strapi (backend + admin) + Next.js (frontend) + PostgreSQL (db) + S3/MinIO (media).
Prerequisites
- Linux VPS 2 vCPU, 4 GB RAM, 20 GB SSD
- Node.js 18+
- PostgreSQL 14+
- Root access
Step 1: Prepare PostgreSQL
sudo apt install -y postgresql
sudo -u postgres psql
CREATE DATABASE strapi;
CREATE USER strapiuser WITH ENCRYPTED PASSWORD 'strong-pass';
GRANT ALL PRIVILEGES ON DATABASE strapi TO strapiuser;
\c strapi
GRANT ALL ON SCHEMA public TO strapiuser;
\q
Step 2: Create Strapi project
cd /var/www
npx create-strapi-app@latest strapi --quickstart --no-run
Wizard:
- Choose between databases: postgres
- Database name:
strapi - Host:
127.0.0.1 - Port:
5432 - Username:
strapiuser - Password:
strong-pass - Enable SSL connection: N
cd /var/www/strapi
Step 3: First start
npm run build
npm run start
Strapi listens on :1337. Visit http://VPS_IP:1337/admin. Create first admin.
Step 4: Define a Content Type
In admin: Content-Type Builder > Create new collection type.
"Article" example:
- title: Text
- slug: UID (generated from title)
- content: Rich Text (Markdown)
- cover: Media (single)
- author: Relation > many-to-one > User
- publishedAt: Datetime
Save. Strapi auto-generates:
- REST API:
GET /api/articles,POST /api/articles... - GraphQL schema if plugin installed
Step 5: Manage permissions
Settings > Users & Permissions Plugin > Roles.
- Public: no auth
- Authenticated: logged in
To make articles publicly readable:
Public > Article > check find and findOne.
Step 6: Test API
curl http://VPS_IP:1337/api/articles
Filter / paginate:
curl 'http://VPS_IP:1337/api/articles?filters[title][$contains]=hello&pagination[limit]=10'
Populate relations:
curl 'http://VPS_IP:1337/api/articles?populate=cover,author'
Step 7: API token
Settings > API Tokens > Create. Type: Read-only, Full access, or custom.
curl http://VPS_IP:1337/api/articles \
-H "Authorization: Bearer YOUR_TOKEN"
Step 8: Enable GraphQL
npm install @strapi/plugin-graphql
Restart. GraphQL playground on :1337/graphql (dev).
query {
articles {
data {
attributes {
title
slug
content
}
}
}
}
Step 9: S3 / MinIO upload
npm install @strapi/provider-upload-aws-s3
config/plugins.js:
module.exports = ({ env }) => ({
upload: {
config: {
provider: 'aws-s3',
providerOptions: {
accessKeyId: env('AWS_ACCESS_KEY_ID'),
secretAccessKey: env('AWS_ACCESS_SECRET'),
region: env('AWS_REGION'),
params: {
Bucket: env('AWS_BUCKET'),
},
},
},
},
});
.env:
AWS_ACCESS_KEY_ID=xxx
AWS_ACCESS_SECRET=xxx
AWS_REGION=eu-west-3
AWS_BUCKET=my-bucket
Step 10: Production with PM2
sudo npm install -g pm2
cd /var/www/strapi
pm2 start npm --name strapi -- start
pm2 startup
pm2 save
Nginx reverse proxy:
server {
listen 443 ssl http2;
server_name strapi.your-domain.com;
client_max_body_size 50M;
location / {
proxy_pass http://localhost:1337;
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;
}
}
Step 11: Backups
PostgreSQL:
pg_dump -U strapiuser -h 127.0.0.1 strapi | gzip > strapi-$(date +%F).sql.gz
Uploads:
tar czf strapi-uploads-$(date +%F).tar.gz /var/www/strapi/public/uploads
Strapi has export/import:
npx strapi export --no-encrypt
npx strapi import --file export.tar.gz
Step 12: Critical env vars
/var/www/strapi/.env:
HOST=0.0.0.0
PORT=1337
APP_KEYS=key1,key2,key3,key4
API_TOKEN_SALT=...
ADMIN_JWT_SECRET=...
TRANSFER_TOKEN_SALT=...
JWT_SECRET=...
DATABASE_CLIENT=postgres
DATABASE_HOST=127.0.0.1
DATABASE_PORT=5432
DATABASE_NAME=strapi
DATABASE_USERNAME=strapiuser
DATABASE_PASSWORD=strong-pass
⚠️ Generate strong secrets:
openssl rand -base64 16
Troubleshooting
"ECONNREFUSED" on PostgreSQL
sudo nano /etc/postgresql/14/main/pg_hba.conf
host all all 127.0.0.1/32 md5
sudo systemctl restart postgresql
Admin panel 502
pm2 logs strapi
Run npm run build after each update.
Slow startup
Strapi 4 loads all content types, plugins, hooks at boot. ~30s is normal.
Memory issues
Increase VPS RAM, or set NODE_OPTIONS=--max-old-space-size=2048 in .env.
Useful commands
npm run develop # dev mode (hot reload)
npm run build # build prod
npm run start # start prod
npm run console # Strapi REPL
npx strapi export --no-encrypt
npx strapi import --file f.tar.gz
pm2 logs strapi
sudo tail -f /var/log/postgresql/postgresql-14-main.log
Conclusion
Strapi gives you:
- Editable backoffice + auto API
- Fine-grained permissions and roles
- Multi-tenant, i18n, drafts
- REST and GraphQL
- Backend / frontend decoupling
Going further:
- Pair with Next.js for frontend (ISR)
- Enable Cloudinary for optimized media
- For large volumes, use Strapi Cloud or K8s cluster
Resources
- Official docs: https://docs.strapi.io
- Site: https://strapi.io
- Plugin marketplace: https://market.strapi.io
- GitHub: https://github.com/strapi/strapi

















