A picture of a padlock securing entry to a yard

Emulating QA and Production Environments with Next.js and Caddy

The best development or QA environment is one that reflects your Production environment as much as possible. The fewer differences there are, the less likely it is that issues will be found in testing or production as a result of configuration or environment deltas.

Sometimes there are challenges which make that impractical. At Zoopla our main website application uses Next.js with a custom express server. The custom express server allowed us to write code just for the local development experience by setting up a HTTPS listener and applying SSL certificates.

Below is the Next.js community provided solutions.

var https = require("https");
var fs = require("fs");

const next = require("next");
const port = parseInt(process.env.PORT, 10) || config.getPort();
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev, dir: __dirname });
const handle = app.getRequestHandler();

var options = {
  key: fs.readFileSync("ssl.key"),
  cert: fs.readFileSync("ssl.crt"),
  ca: [fs.readFileSync("root.crt")],

app.prepare().then(() => {
    .createServer(options, (req, res) => {
      // handle ....
    .listen(port, (err) => {
      if (err) throw err;
      console.log(`> Ready on localhost:${port}`);

Whilst our process was simple, it did provide two concerns.

  1. We are tied into using a custom express server, something that would be incompatible with our engineering strategy.
  2. We have mixed production and local development code, making debugging harder.

The solution - Caddy

We've been refactoring our code recently in order to remove the custom express server and open up more opportunities for us. This meant refactoring away the HTTPS implementation and replacing it with something equal in capability but not tied into the code itself. That's when we discovered Caddy. There is an old saying, "the perfect tool for the job" and Caddy was just that for us.

To get set up we needed to create a Caddyfile and with just 2 lines of code we had a working solution.



Caddy acts as a proxy server and when we access https://local.zoopla.co.uk Caddy handles the request which is forwarded onto our application running locally. Caddy also handles the creation and trusting of a locally signed SSL certificate which removed an error prone manual step our engineers were taking.

Bridging the gap between our systems - an added bonus

Using Caddy we can also provide the additional benefit of emulating our QA and Production environments better.

As with most companies, Zoopla has legacy code and some of that legacy code is used to power the Zoopla website. What this means for engineers running the applications locally is that some pages can be inaccessible due to them running on legacy systems which are often too complicated to be emulated in a development environment.

If an engineer really needs to test across systems, an engineer needs to run the old application too and connect the dots between them.

With Caddy we are able to solve this problem by forwarding the requests onto our QA environment which gives engineers running the application locally a seamless experience of our website, something we've been missing for some time now!

@qa {
    path /legacy-page-on-zoopla/

reverse_proxy @qa https://qa-server-address {
    header_up host qa-server-address


With these few lines of code we've been able to provide our engineering teams a much better emulation of our QA and Production environments, plus we got to finally remove the custom express server! 🚀