Update: December 2021

Since writing this article, Cloudflare has released a new feature to allow hosting multiple origin endpoints with one installation of cloudflared.

While this approach is useful, I mainly still use a variation of my technique below because this allows the tunnels to be kept logically separate - for example they can be restarted independently. It's also useful if you want to use the service dependency features of Windows Services to restart a specific tunnel when another service restarts, without affecting other tunnels hosted on the same server. The official Cloudflare approach doesn't provide this flexibility.

Cloudflare Tunnel only supports installing one instance of a Windows Service by default, but you can use Windows' sc.exe command to manually install as many instances as you need.

Though I only focus on Windows here, it's possible to do the exact same thing with multiple systemd services on Linux.

Background

Cloudflare Tunnel provides a quick and easy way to setup reverse proxy tunnels for both dev and production. It's enabled by running the cloudflared daemon locally on Windows, Mac or Linux, and the official docs provide a simple and easy to follow getting started guide.

For production use, having the cloudflared daemon survive server restarts is a must, and the recommended approach on Windows is to install it as a Windows Service. However there is currently no officially supported way to install multiple instances of the cloudflared service on Windows.

This is how the Cloudflare docs suggest installing cloudflared as a service, using their built in helper command:

PS C:\Cloudflared> .\cloudflared.exe service install

It seems that the service name Cloudflared is hardcoded and there is no override to allow multiple instances to be installed. Nor do they intelligently check for an existing instance and create a unique service name on the fly. Try this command more than once, even from a copy of  cloudflared in a different directory, and you'll see this error:

INFO[0000] service Cloudflared already exists

Cloudflare's current workaround for this is via some CNAME DNS entries and configuring "multiple hostnames" in your configuration file, but this feels like a bit of a hack and doesn't provide good separation for running multiple independent tunnels from one box. (For example, all your logging for multiple tunnels goes into one file, plus restarts and configuration changes affect all tunnels - not good).

What makes this even worse, is cloudflared looks in a hard-coded system directory for its YAML config file when running as a service.  To override this default directory requires a manual registry edit after installing the service. Yuck!

I found myself wanting either a way to define multiple tunnels in the cloudflared YAML config file, or have the ability to install multiple instances of the service.  And I wanted my config stored somewhere sensible, with the aim to having it deployed/configured dynamically by something like Ansible or Octopus Deploy.

Thankfully, there is a way to do this...

Setting up Multiple Cloudflared Windows Services

We'll use the built-in sc.exe Windows service control tool to install our cloudflared services instead of Cloudflare's own helper command.

I'm going to assume you're already familiar with the basics of setting up an Cloudflare Tunnel and authenticating with your Cloudflare account.  If you haven't yet got that far, I'd suggest following their quick start guide first.

For this exercise, I am setting up an Cloudflare Tunnel for a standalone microservice API and a separate admin dashboard web app. Both are hosted in IIS and bound to separate ports on localhost. (One of the main advantages of Cloudflare Tunnel is you can just bind all your web apps / APIs to something like http://localhost:8080, with no firewall ports opened up.  Once configured, cloudflared will reverse proxy all traffic from an outside Cloudflare-hosted domain name at https://mysecureapp.example.com to that localhost binding.You get TLS for free and no other network config is required).

To setup my two tunnels, first I need to define a configuration file for each tunnel. I prefer to have the cloudflared.exe binary, my Cloudflare authentication certificate and my config files all in one directory, however it would also be perfectly valid to put cloudflared.exe in somewhere like c:\Program Files\Cloudflare and have your config/log files elsewhere.

My cloudflared configuration files for the two tunnels look like this:

hostname: api.example.org
url: http://localhost:8080
logfile: D:\Cloudflared\tunnel_api_log.log
origincert: D:\Cloudflared\cert.pem
hostname: dashboard.example.org
url: http://localhost:8081
logfile: D:\Cloudflared\tunnel_dashboard_log.log
origincert: D:\Cloudflared\cert.pem

Once saved, my Cloudflared folder contents look like this:

Figure 1 Cloudflare binary, authentication certificate and 2x tunnel config files

Time to install the Windows services. First I'll install the tunnel service for my microservice API with the following command (adjust the file paths accordingly):

sc.exe create Cloudflared-Api binPath='\"D:\Cloudflared\cloudflared.exe\" --config \"D:\Cloudflared\tunnel_api_config.yml\"' displayname="Cloudflare Tunnel - Api" start=auto

We pass in the unique path to our config file as part of the binary path in the service definition. This allows us to override the default behaviour of looking for config files in the hardcoded system path.  Not only do I dislike having app-specific configuration buried deep in obscure windows system folders, it would also prevent us from having multiple service instances, because they'd all look for the same config file. Overriding this config file path when we create the Windows Service is fundamental to unlocking the ability to have as many instances of Cloudflare Tunnels as we like.

All Windows services across the OS must have a unique name (Cloudflared-Api in this case) and a unique display name (Cloudflare Tunnel - Api).  These are my choices but you can pick any name and display name to suit. (Wherever you see Cloudflare-Api in the subsequent commands below, you should substitute in whatever name you picked for your service instance.)

You'll note that this service is also set to startup automatically, which is what will let it recover gracefully from server reboots.

When you use the cloudflared built-in helper command to install the service, it also configures automatic service recovery options, which allow the service to restart itself automatically if it exits unexpectedly. This is optional, but if you wish to replicate these recovery options, you can do so with this command:

sc.exe failure Cloudflared-Api reset=86400 actions=restart/1000/restart/1000/restart/1000

Next, I advise setting a description on the service to help you identify what each tunnel is (especially important if you have several of them):

sc.exe description Cloudflared-Api "Cloudflare Cloudflare Tunnel - Microservice Api"

And lastly, if you have any other Windows services that your tunnels should depend on, you can add these dependencies with the following command, to ensure that your tunnels do not start until their dependant services have also started. In my case, because both my API and web dashboard applications are hosted in IIS, I chose to configure my tunnels to depend on the W3SVC IIS service:

sc.exe config Cloudflared-Api depend=W3SVC

That's it. Repeat the above series of commands for your second (and more) services, substituting in unique names, display names and config file paths, and you can setup as many distinct cloudflared instances as you need.

All that's left is to start your tunnels (repeat for each instance you installed):

sc.exe start Cloudflared-Api

You should now be able to see all your instances happily co-existing in the Windows Service list:

Figure 2 Multiple Cloudflare Tunnel Windows Services

And the separate log files are created automatically for each instance:

Figure 3 Separate log files for each Cloudflare Tunnel Windows Service

Now I can safely restart either service without affecting the other, making config changes less disruptive. I could even choose to use a distinct copy of the cloudflared binary per service, to allow each service to run on its own version of the binary if I wanted that level of granularity.

Summary

Hopefully Cloudflare will bake in some of this functionality into a future version of cloudflared (edit: they did, sort of) but until then, I hope you find this approach useful.

Note: There currently seems to be a bug causing the cloudflared service to sometimes display some odd behaviour when stopping it. This seems to happen regardless of whether you use the official cloudflared helper method for installing the service, or the sc.exe approach as outline above.  For me, it doesn't cause major issues, I can still stop the services and server restarts are unaffected.