How to set up a Plone site to serve an entire Web domain

published Dec 21, 2021

Plone can do this job for you too. Here is a quick guide to understand how it's done.

How to set up a Plone site to serve an entire Web domain

You've installed Plone on your Web server machine.  You've set up NginX (or a similar Web server) to proxy visitors of site.com to your Plone server and take care of your SSL certificate (perhaps with Let's Encrypt).  You've even created a site.com Plone site.  The whole setup looks more or less like this.

client <--HTTPS-->  NginX  <--HTTP--> Plone
web site.com localhost
browser port 443 port 8080

But something is clearly wrong.  When you go to site.com on your browser, what you see is this:

This is clearly not what you signed up for.  In fact, in order to see your Plone site, you have to click on View your Plone site.  Only then does your site appear — at URL site.com/site.com.  That's... not optimal.

Worry not — everyone goes through this, and there's a very easy fix.

URL rewriting gets your visitors to your actual site

You want to map visits of https://site.com/ to your real site's Plone server URL http://ploneserver/site.com/.  If your visitor visits https://site.com/blog/my-latest-blog-page, you want Plone to respond with the contents of the page http://localhost/site.com/blog/my-latest-blog-page.

Perhaps to that effect, you might have tried this NginX rewrite rule:

server {
listen 443;
server_name site.com;
access_log /var/log/nginx/site.com.access.log;
error_log /var/log/nginx/site.com.error.log;
location / {
proxy_pass http://localhost:8080/site.com/;
}
}

However, simply proxying requests using NginX URL rewrite rules would not yield a working site.  A naive proxying using NginX rules would cause Plone to serve pages with style / image / script references to URLs all starting with http://localhost:8080/site.com.  Now nothing (besides the HTML pages) loads, and all links are broken — arguably worse than before.

So, in addition to the inbound URL rewrite that NginX does, Plone somehow must be informed of the origin URL address your visitor requested, in order to produce and serve a correct page for him.

You are in luck.  Plone can do that out of the box.  It isn't even difficult.

Plone virtual hosting: enter the VirtualHostMonster

The Virtual Host Monster is a facility that ships in Plone, which allows you to run any number of Web sites inside your Plone instance, all with their independent domain name or address.

The VHM documentation tells us:

A Virtual Host Monster’s only job is to change the URLs which your Zope objects generate. This allows you to customize the URLs that are displayed within your Zope application, allowing an object to have a different URL when accessed in a different way. This is most typically useful, for example, when you wish to “publish” the contents of a single Zope Folder (e.g. ‘/FooFolder’) as a URL that does not actually contain this Folder’s name (e.g as the hostname ‘www.foofolder.com’).

What's more important for you, is that VHM lets you map a domain name to a site, or even a single area of your site, to any URL you want (which is presumably both reachable by your clients and served by your server), and always get the correct URLs on the served pages.

In short: what you want to do is make your rewrite rule go through the Virtual Host Monster.

This example that will get your site.com situation fixed:

server {
listen 443;
server_name site.com;
access_log /var/log/nginx/site.com.access.log;
error_log /var/log/nginx/site.com.error.log;

# Note that domain name spelling in VirtualHostBase URL matters
# -> this is what Plone sees as the "real" HTTP request URL.
# the second "site.com" in the URL is your site id (case sensitive)
location / {
proxy_pass http://localhost:8080/VirtualHostBase/https/site.com/site.com/VirtualHostRoot/;
}
}
# Don't forget to reload NginX!

Let's go over the bolded rewrite rule in detail, part by part:

  1. proxy_pass: proxy incoming requests to the URL following this keyword.
  2. http://localhost:8080/: the proxied requests must be retrieved from port 8080 of the local server.
  3. /VirtualHostBase: the proxied request must pass through the VHM.
  4. /https: tell Plone that the origin request came via HTTPS.
  5. /site.com (the first one): tell Plone that the origin request is for domain site.com.
  6. /site.com (once again): tell Plone that the object to serve is your Plone site called site.com.
  7. /VirtualHostRoot/: tell Plone that the site.com object is to be served as the root of your site; this finishes the rewrite rule.

This is how the Virtual Host Monster learns of — and how it tells Plone — the key elements necessary to construct correct URLs: host name, port, and object to serve to your visitors.

Presto!  Your site is now operational at site.com:

But how do I access the root of my Plone server?

Aha!  If you paid close attention, you might have noticed one little detail — the work we did above shadowed the root of the Plone server, making it impossible to do administrative tasks, like adding a new Plone site, or accessing the Zope Management Interface (ZMI) at the /manage URL.

Don't worry, though — we can add another rewrite rule to fix that.  As it turns out — forgive me if this turns too technical — you can also instruct the Virtual Host Monster to serve a completely different namespace of your Web server under a subdirectory of your URL hierarchy.  This has a name — it's called inside-out hosting.

In this case, we are going to expose the root of your Plone server — the thing that contains the site.com site — under the URL /zope.  Here is an example with everything already baked in:

server {
listen 443;
server_name site.com;
access_log /var/log/nginx/site.com.access.log;
error_log /var/log/nginx/site.com.error.log;

# Note that domain name spelling in VirtualHostBase URL matters
# -> this is what Plone sees as the "real" HTTP request URL.
# the second "site.com" in the URL is your site id (case sensitive)
 location /zope {
proxy_pass http://localhost:8080/VirtualHostBase/https/site.com/VirtualHostRoot/_vh_zope;
}
location / {
proxy_pass http://localhost:8080/VirtualHostBase/https/site.com/site.com/VirtualHostRoot/;
}
}
# Don't forget to reload NginX!

Here is the simple explanation of the new rule we just added:

  1. proxy_pass: proxy incoming requests to the URL following this keyword.
  2. http://localhost:8080/: the proxied requests must be retrieved from port 8080 of the local server.
  3. /VirtualHostBase: the proxied request must pass through the VHM.
  4. /https: tell Plone that the origin request came via HTTPS.
  5. /site.com (the only one): tell Plone that the origin request is for domain site.com.
  6. /VirtualHostRoot/: tell Plone that the root object is to be served as the root of the request.
  7. /_vh_zope: tell Plone that the served object should be served as if it was located under/zope; this finishes the rewrite rule.

Now you can access https://site.com/zope/manage and it will work just fine.

Conclusion

Plone is truly versatile in how it serves Web visitors — it can host multiple sites for you, and it can also completely transform the URL namespace of your Web services.  I highly recommend Plone for these, and other, reasons.