Right, so you’ve got a Hugo site. It’s a pile of templates, content files, and configuration sitting on your hard drive. Not exactly a thrilling view for a web browser. The hugo server command is the magician that brings this inert pile of code to life, right in front of you. It’s your development playground, your live-preview engine, and your number one tool for actually seeing what you’re building.

Think of it less as a “server” and more as a hyper-observant construction foreman. You change a file, he yells “EVERYTHING IS DIFFERENT NOW,” and instantly rebuilds the site so you can see the result. It’s glorious.

Starting the Engine

The simplest invocation requires no thought whatsoever. Navigate to the root of your Hugo project (the directory with your config.toml or hugo.toml file) and run:

hugo server

You’ll be greeted with a flurry of text telling you what it’s doing, but the only line that truly matters is this one:

Web Server is available at http://localhost:1313/ (bind address 127.0.0.1)

localhost is your own computer, and 1313 is the port Hugo has chosen as its default stage. Open that URL in your browser. Boom. There’s your site. It looks… well, it probably looks like a default theme, which is to say a bit like a developer dressed it, but it’s alive. This is where the magic happens.

The Flags You’ll Actually Use

The bare hugo server is fine, but you’re not a basic user. You’re a professional. Here are the flags that will become second nature.

-D, –buildDrafts: This is non-negotiable. By default, Hugo pretends your draft content (anything with draft: true in its front matter) doesn’t exist. This is sensible for a final build but infuriating for development. This flag tells the server to include drafts so you can actually preview that blog post you’re writing.

-E, –buildExpired: Similarly, content with a future publishDate is ignored. This flag brings it into the fold.

-F, –buildFuture: The same as -E, but for content with a future date. I honestly can’t remember the nuanced difference the Hugo creators intended between -E and -F, and I’ve been doing this for years. I just use -D and get on with my life.

My standard, everyday development command is:

hugo server -D

It’s the equivalent of “make it work and show me everything.”

Live Reload: The Real Superpower

The default behavior, which you’ve probably already noticed, is live reload. Change a template, a CSS file, or a content file, and watch your browser refresh itself almost instantly. You don’t hit save and then fumble for the refresh button. You just work, and the site keeps up. It’s the single best feature for developer quality of life.

This works because the server injects a tiny bit of JavaScript into your pages to open a WebSocket connection back to the Hugo process. When Hugo sees a file change, it shoots a message down that pipe saying “hey, reload now.”

The Pitfall: Sometimes, it gets confused. If you make a drastic error in your template—like a syntax mistake that breaks the entire build—the live reload might fail silently. The server logs will scream at you in terminal, but your browser will just sit there, looking dumb. Always keep an eye on the terminal output. It’s the truth-teller.

Binding to the Network (And Why You Might)

By default, the server binds to 127.0.0.1 (localhost). This means it’s only accessible from your own machine. This is secure and sensible.

But sometimes you need to show your in-progress site to a colleague on your local network. For that, you need to bind to your machine’s real IP address, making it accessible to others. First, find your local IP (on macOS/Linux, ipconfig getifaddr en0 or hostname -I; on Windows, ipconfig). Let’s say it’s 192.168.1.42. Then you run:

hugo server --bind="192.168.1.42" --baseURL="http://192.168.1.42:1313"

The --bind flag tells the server where to listen. The --baseURL flag is crucial because Hugo generates all its links absolutely. If you don’t set this, the site will still try to load its CSS and images from localhost:1313, which will fail on your colleague’s machine. You’re telling it, “For this particular build, the root of the site is at this other address.”

You can also use --bind="0.0.0.0" to bind to all interfaces, but you’ll still need to set the correct --baseURL.

When Fast Render Isn’t Fast Enough

You might see a --disableFastRender flag and wonder what it does. The “fast render” mode is the default: when you change a single page, Hugo only re-renders that page to send to the browser. This is, as the name implies, fast.

However, if you’re doing something complex where changing one page should affect others (e.g., you’re using a custom range that aggregates content across the site), the fast render might not pick up those secondary changes. You’ll be left scratching your head why your change didn’t propagate. In this rare case, using --disableFastRender forces a full site rebuild on every change. It’s slower, but it guarantees everything is consistent. It’s a useful debugging step when the live preview seems to be lying to you.

The development server is Hugo’s killer feature. Use it, master its flags, and trust it to keep up with you. Just remember to check its logs when things get quiet—it’s usually because it’s trying to tell you something important.