Skip to main content
Version: 7.0

Self-hosting Redwood (Serverful)


This doc has been deprecated in favor of the Baremetal docs.

Do you prefer hosting Redwood on your own server, the traditional serverful way, instead of all this serverless magic? Well, you can! In this recipe we configure a Redwood app with PM2 and Nginx on a Linux server.

A code example can be found at, and can be viewed live at


You should have some basic knowledge of the following tools:


To self-host, you'll have to do a bit of configuration both to your Redwood app and your Linux server.

Adding Dependencies

First add PM2 as a dev dependency to your project root:

yarn add -D pm2

Then create a PM2 ecosystem configuration file. For clarity, it's recommended to rename ecosystem.config.js to something like pm2.config.js:

yarn pm2 init
mv ecosystem.config.js pm2.config.js

Last but not least, change the API endpoint in redwood.toml:

- apiUrl = "/.redwood/functions"
+ apiUrl = "/api"

Optionally, add some scripts to your top-level package.json:

"scripts": {
"deploy:setup": "pm2 deploy pm2.config.js production setup",
"deploy": "pm2 deploy pm2.config.js production deploy"

We'll refer to these later, so even if you don't add them to your project, keep them in mind.

Linux server

Your Linux server should have a user for deployment, configured with an SSH key providing access to your production environment. In this example, the user is named deploy.


Typically, you keep your Nginx configuration file at /etc/nginx/sites-available/redwood-pm2 and symlink it to /etc/nginx/sites-enabled/redwood-pm2. It should look something like this:

server {
listen 80;

location / {
root /home/deploy/redwood-pm2/current/web/dist;
try_files $uri /index.html;

location /api/ {
proxy_pass http://localhost:8911/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;

Please note that the trailing slash in proxy_pass is essential to correctly map the API functions.


Let's configure PM2 with the pm2.config.js file we made earlier. The most important variables are at the top. Note that the port is only used locally on the server and should match the port in the Nginx config:

const name = 'redwood-pm2' // Name to use in PM2
const repo = '' // Link to your repo
const user = 'deploy' // Server user
const path = `/home/${user}/${name}` // Path on the server to deploy to
const host = '' // Server hostname
const port = 8911 // Port to use locally on the server
const build = `yarn install && yarn rw build && yarn rw prisma migrate deploy`

module.exports = {
apps: [
node_args: '-r dotenv/config',
cwd: `${path}/current/`,
script: 'yarn rw serve api',
args: `--port ${port}`,
env: {
NODE_ENV: 'development',
env_production: {
NODE_ENV: 'production',

deploy: {
production: {
ref: 'origin/master',
ssh_options: 'ForwardAgent=yes',
'post-deploy': `${build} && pm2 reload pm2.config.js --env production && pm2 save`,

If you need to seed your production database during your first deployment, yarn redwood prisma migrate dev will do that for you.

Caveat: the API seems to only work in fork mode in PM2, not cluster mode.


First, we need to create the PM2 directories:

yarn install
yarn deploy:setup

Your server directories are now set, but we haven't configured the .env settings yet. SSH into your server and create an .env file in the current subdirectory of the deploy directory:

vim /home/deploy/redwood-pm2/current/.env

For example, add a DATABASE_URL variable:


Now we can deploy the app! Just run the following; it should update the code, take care of database migrations, and restart the app in PM2:

yarn deploy

Enjoy! 😁