Running a Hugo server on local and public networks
When writing posts or updating the layout of this website, I run Hugo’s local server to view my changes live. Sometimes I want to view my website from a different device on my network to check how it renders the page. Thankfully, Hugo’s server has some builtin flags to bind the server to my machine’s IP address to allow viewing from any machine on the network.
Run the Hugo server on a local network
By default hugo server
will create a server running on localhost port
1313. If your IP address is 192.168.1.20
, providing the following flags to
Hugo will will bind the server to your device’s IP address on your local
network instead of localhost. Substitute your own IP address in place of my
example address.
$ hugo server --bind 192.168.1.20 --baseURL http://192.168.1.20/
That’s all it takes! After doing this, any device on your local network can
access the Hugo development server by visiting http://192.168.1.20:1313
.
The --bind
flag binds the server to the given IP address rather than
localhost. The other important flag is --baseURL
which Hugo uses for prefixing
internal links on the webpage. If you don’t supply a base URL, any links that
Hugo creates will be prefixed with http://localhost:1313
which will not work
for devices other than the one running the server.
This covers most of my needs when I want to view my Hugo website on a different device, but I occasionally need to share access to my development server with someone outside my network.
Access the development server from a public URL
There are some tools that can create a temporary public URL that connects directly to a server running on your machine. Two services that I am aware of are ngrok and Cloudflare Tunnel. I use Cloudflare Tunnel, but both can be used to accomplish the same task.
Cloudflare Tunnel creates an encrypted tunnel between your server and Cloudflare’s nearest data center without requiring you to open public ports on your network. The program is called cloudflared and is easy to install and use. Here are the docs for installing cloudflared for Linux, MacOS, or Windows.
The Cloudflare docs are focused on creating persistent tunnels registered on
your own domain, and only briefly cover the TryCloudflare service. There is no
need to authenticate cloudflared or create config files to use the subdomains of
trycloudflare.com
. You don’t even need a Cloudflare account! Here is more
documentation on TryCloudflare.
Once installed the following command will create a temporary tunnel. The --url
flag specifies which local server to connect the tunnel to.
$ cloudflared tunnel --url http://localhost:1313
...
2021-08-21T01:30:49Z INF +------------------------------------------------------------+
2021-08-21T01:30:49Z INF | Your free tunnel has started! Visit it: |
2021-08-21T01:30:49Z INF | https://cloudflare-example-subdomain.trycloudflare.com |
2021-08-21T01:30:49Z INF +------------------------------------------------------------+
...
On the Hugo side It is important to set the base URL to the Cloudflare tunnel
domain. The --appendPort=false
flag prevents Hugo from appending the port to
the links. Without this flag, Hugo will create links like
https://cloudflare-example-subdomain.trycloudflare.com:1313/example-path
,
which will not load properly because Cloudflare is not serving the tunnel over
port 1313.
$ hugo server --appendPort=false --baseURL https://cloudflare-example-subdomain.trycloudflare.com
No matter how many times I start a tunnel, the simplicity of cloudflared always impresses me. While slightly more involved than running a Hugo server on a local network, cloudflared makes it trivial to create a temporary public link to your development server. The link will no longer work once you close the Cloudflare tunnel.
A fish function to automate local and public servers
I find myself needing local and public access to my Hugo servers on a regular
basis. To automate this process somewhat, I created a fish function to wrap
hugo
. Feel free to use this function directly, or as inspiration for your own
fish or bash scripts. It isn’t very robust, but it works well enough for me.
function hugo -a command
switch $command
# run on the local network
case l local
# to trim off "l" or "local" from the other args
set argv $argv[2..]
set ip_addr (hostname -i)
command hugo server --bind $ip_addr --baseURL "http://$ip_addr" $argv
# run on a public network with cloudflared
case p public
# to trim off "p" or "public" from the other args
set argv $argv[2..]
# create a temporary file
set tmpfile (mktemp)
cloudflared tunnel --url http://localhost:1313 2> $tmpfile &
# wait for the server to start
echo -n Waiting for cloudflare tunnel to start
set cloudflare_url
while test -z $cloudflare_url;
echo -n .
set cloudflare_url (grep --color=never -o -m1 "http.*trycloudflare.com" $tmpfile)
sleep 1
end
echo -e " started\n"
# run hugo through the tunnel
command hugo server --appendPort=false --baseURL $cloudflare_url $argv
echo -e "\nShutting down tunnel"
kill $last_pid
sleep 1
# fall through to all other uses of hugo
case "*"
command hugo $argv
end
end
With this function I can run hugo local
to run a server on my local network,
or hugo public
to run a server accessible through a Cloudflare Tunnel.
Additional arguments like -D
can still be passed to enable draft post
visibility.