How to host PayloadCMS on a subdomain and subpath with nginx
Sometimes your tech stack's complexity is a little more advanced and you need to host an API on a subdomain or a subpath (or both!) and it can get confusing if, like me, you only do devops when you have to.
The configurations we're going to cover here aren't in any way specific to Payload or even nginx, so you will be able to apply these same principles to other apps and servers such as apache2 or caddy.
We will cover both subdomain and subpath hosting to cover the most common use cases and we're going to use our prototyping space nouance.dev as the example primary domain.
Prerequisites
- a configured server with Payload running on it, see the end for resources on this
- base knowledge of nginx and server configuration
Test your deployment works by checking that you can reach your Payload site via your server's IP and the port, usually something like <ip>:3000/admin.
DNS configuration
Let's get the most uncertain part out of the way and configure our DNS. Depending on your provider, these changes might propagate pretty quickly but in some cases it can also take up to 24 hours. If you're doing a lot of DNS changes and rapidly prototyping setups, we recommend a service like Cloudflare which is often very fast to propagate your changes worldwide.
We will create a new A record with the host being api
and then pointing it to our server's IP address.
Subdomain hosting
This is by far the easiest to set up and not at all that different to domain hosting and you may already have this step working, so you can skip further down where we move Payload to be hosted on a subpath.
Payload configuration
Set your serverURL
in the payload.config.ts
file as we did here:
1// payload.config.ts234// ...56export default buildConfig({7 serverURL: 'https://api.nouance.dev',89// ...
Nginx configuration
Below is roughly the nginx configuration you're looking to run. After this is setup you can run certbot and it will make some changes to it on its own to add a redirect from http to https and for it to listen on port 443 instead of 80.
1server {2 listen 80;34 server_name api.nouance.dev;56 location / {7 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;8 proxy_set_header Host $host;9 proxy_pass http://127.0.0.1:3000;10 proxy_http_version 1.1;11 # proxy_buffering off; # we will come back to this12 proxy_set_header Upgrade $http_upgrade;13 proxy_set_header Connection "upgrade";14 }15}
We've commented out proxy buffering for now to leave it "on" by default. We will explain below in our troubleshooting step how it works and what you can do if you encounter issues.
Rebuild Payload to make sure your updated configuration is being used and then restart nginx with service nginx restart
(it may need sudo).
That's it! Make sure your server is running and now you should be able to access it via your subdomain.
Subpath hosting
One common requirement is that you may want all of your APIs or endpoints to exist on the same subdomain but to then access them on a different subpath.
Here is a hypothetical tech structure:
api.nouance.dev/ <- our documentation site
- api.nouance.dev/payload <- Payload
- api.nouance.dev/chat <- chat bot API
These subpaths can be anything and you can choose what you need. We recommend separate subdomains as it tends to simplify things a lot for your developers.
Payload configuration
To support subpaths we will need to change our payload configuration with the following custom routes being set, see the docs for a full explanation.
1// payload.config.ts23// ...45routes: {6 api: '/payload/api',7 admin: '/payload/admin',8 graphQL: '/payload/graphql',9 graphQLPlayground: '/payload/graphql-playground',10},1112// ...
Nginx configuration
Now we should go back to our nginx configuration to catch only /payload locations for this particular reverse proxy. Simply change your /
location to /payload
and then rebuild your Payload app to have the updated config and restart nginx and that's it!
1// payload.conf23// ...45location /payload {67// ...
What if my app doesn't run on a subpath?
So there's a third way to host content on a subpath, this doesn't apply to Payload due to the above configuration of the routes, however if we want to serve a subpath to a localhost that doesn't take it we can actually configure our subpath to rewrite the internal URL.
1// payload.conf23// ...45location /myapp {6 rewrite ^/myapp/(.*)$ /$1 break;7 // ... same configuration as before ...8}910// ...
So this little rewrite line will now take any path starting with /myapp
and rewrite internally to /
so that our localhost application responds to it as expected and you can use this to serve static files too.
DigitalOcean has a great tutorial expanding on this nginx directive.
Useful tips and troubleshooting
Remember that certain TLDs such as .dev require https connections by default in modern browsers, so while it can be tempting to leave the cert generation until the end we recommend you get it out of the way sooner than that.
Adding more locations/paths
In the same server block you can add multiple locations if you want to reverse proxy to other applications on your server. Just remember that nginx' location matching works in order from top to bottom and uses regex matching, so if you want to have a /
catch all it has to be at the end.
Proxy buffering
We said we'd come back to this. This part here seems to highly depend on many factors but if you're having issues properly accessing Payload's API or admin panel, you might find it useful to uncomment that line and disable proxy buffering via proxy_buffering off;
.
In nginx, proxy buffering is a form of cache nginx uses to cache responses from your proxied endpoint, so it can serve them directly from its own memory cache which improves performance a lot as your localhost app may not need to be hit again however this can also cause issues when dealing with a more dynamic API such as Payload's depending on what you're doing.
As you can imagine it wouldn't be an issue if you're just fetching static data, but otherwise it can cause you to receive expired data or, more commonly noticed, incorrectly cached proxy responses as we've seen in a few situations.
X-Accel headers
In direct continuation with the above, you can also bypass nginx' response buffering cache via a header added to your requests.
Set X-Accel-Buffering
to "no"
, so for example res.setHeader("X-Accel-Buffering", "no");
.
This tells nginx to turn off response caching for this request. You can view the full list of available headers from nginx here.
Resources
- Payload deployment documentation, contains references to setting up everything you'll need
- DigitalOcean has brilliant tutorials that apply to any VPS server
- The official nginx documentation is not as user friendly but you'll find more in-depth explanations around how some features work
If you found this useful, follow us on Twitter to keep up to date with our future work and let us know what other areas of the tech stack we could help out with.