If you're looking to truly understand how NGINX works under the hood, building it from source is the best way to do it. In this tutorial, I’ll walk you through how to build and modify NGINX from source on Ubuntu (or WSL, if you're using Windows). But we won’t stop at just building it — we’ll also dive into the configuration and build scripts to understand how NGINX checks for system capabilities and prepares itself to be built.


Why Build NGINX from Source?

If you're someone who loves understanding systems at a deep level, building from source is a must. It lets you peek into how things are wired internally, how configurations are set, how features are toggled, and what the compiler is doing under the hood. You’ll walk away not just with a binary — but with knowledge.


Step 1: System Update

Make sure your system is updated:

sudo apt update

On Fedora, you’d run sudo dnf update.


Step 2: Install Build Tools

NGINX is written in C, so we’ll need a C compiler and a build tool:

sudo apt install gcc make

gcc stands for GNU Compiler collection its mostly used for C/C++ development but also has supports for other languages as well.
At its core, GCC takes your source code (like .c or .cpp files) and compiles it into machine code — typically a binary executable your computer can run. It's the magic bridge between human-readable code and the stuff computers actually execute.

make is a build automation tool. It looks at your files, checks what needs to be (re)built based on changes, and runs the necessary commands — usually compilation — only for what’s changed.
It uses a file called a Makefile to know how to build your project.


Step 3: Install Required Libraries

NGINX uses PCRE for regex, zlib for compression, and optionally OpenSSL for HTTPS support:

sudo apt install libpcre3-dev zlib1g-dev libssl-dev

libpcre3-dev stands for Perl Compatible Regular Expressions. It is a library that provides functions for working with regular expressions, offering syntax and behavior similar to Perl. This makes it powerful and flexible for pattern matching in text. It's commonly used in various applications such as web servers, text processors, and parsers where advanced string matching is required. Installing the -dev version provides the necessary header files (.h) and static/shared libraries required for compiling C or C++ programs that depend on PCRE, making it essential for building software from source that uses regular expressions.

zlib1g-dev is the development version of the widely-used zlib library, which provides functions for compression and decompression using the DEFLATE algorithm — the same algorithm used in formats like gzip and PNG. It is a crucial dependency for software that needs to handle compressed data, including web servers, file archivers, and network protocols that support compression. Installing the -dev package gives you access to the necessary header files and linkable libraries required to compile C/C++ programs that utilize zlib for compressing or decompressing data.

libssl-dev If you plan to use SSL certificates to encrypt traffic with TLS.


Step 4: Clone NGINX Source

Now get the source:

git clone https://github.com/nginx/nginx.git
cd nginx

Image description


Step 5: Run Configuration Script

NGINX comes with its own configuration script in the auto directory. Run it like this:

auto/configure

Image description

This generates the objs/Makefile, which is what make uses to compile the project.

But let’s not stop here — let’s dive into what’s happening behind the scenes.

🧠 A Deep Dive into the Build Files: How NGINX Configures Itself

NGINX doesn’t use autoconf or CMake. It uses its own set of handcrafted scripts inside the auto/ directory to probe system capabilities and generate the Makefile. It’s fascinating and minimal — and gives you a front-row seat to how things really get wired up before compilation.

Image description

Let’s walk through some of the highlights.


🔍 auto/feature: Dynamic Feature Detection

Here’s how NGINX checks for features like epoll, sendfile, or kqueue:

ngx_feature="epoll"
ngx_feature_name="NGX_HAVE_EPOLL"
ngx_feature_test="epoll_create(1);"
. auto/feature

What this does:

  • Sets the feature name and a macro to define.
  • Provides a snippet of C code to test.
  • The auto/feature script generates a small test program using the snippet and tries to compile it.

If compilation succeeds, the feature is assumed to be available, and the macro (e.g., NGX_HAVE_EPOLL) is defined in the generated header files.

Now, you might wonder: how does auto/feature access variables like ngx_feature_test?

Here’s the punchline:

Bash uses dynamic scoping. That means a sourced script or function has access to all variables in its caller’s scope — based on the runtime call stack. There you go — dynamic scoping explained in two sentences.

It’s elegant, powerful, and surprisingly readable once you know the trick.


🧩 What Happens Inside auto/feature

  • It takes the ngx_feature_incs, ngx_feature_test, and other config values.
  • Dynamically generates a test C file with includes and the test snippet.
  • Compiles it using the configured compiler.
  • If the resulting executable builds and runs, NGINX enables that feature.

This is how NGINX verifies not just headers — but full compile+run support for every system feature.


🔨 auto/configure: The Main Entry Point

The configure script ties everything together. It:

  • Calls various sub-scripts for compiler, OS, modules, etc.
  • Calls auto/make at the end to generate the objs/Makefile.

From there, make can pick it up and start compiling.

I focused mainly on the major and interesting pieces, but here’s a quick summary of what you’ll find in the auto/ directory:


🧩 Other Noteworthy Build Scripts

File Purpose
auto/cc/conf Sets compiler and linker flags.
auto/headers Loads system headers (Linux-specific, skipped for Windows).
auto/os/conf Detects OS and sets platform-specific configs.
auto/threads Checks for threading support. (Sorry Windows — no threads for you 😎).
auto/modules Dynamically determines which modules (HTTP, Mail, etc.) to include.
auto/module Handles inclusion of each individual module (called from auto/modules).
auto/lib/conf Loads configuration scripts for specific libraries.
auto/lib/make Adds library-specific rules to the Makefile.
auto/install Defines how NGINX will be installed after compilation.
auto/stubs Provides fallback stubs for unsupported features or modules.

Together, these files form a highly modular, Bash-driven build system that’s surprisingly readable and hackable — once you understand how Bash scoping and sourcing work.


That's a very brief overview if we were to write every single details then this article wont suffice.


Step 6: Compile NGINX

Once configured, build it:

make

You’ll get your binary at objs/nginx.


Step 7: Run It

Let’s test if it worked:

cd objs
./nginx -h

Image description


Step 8: Modify the Source Code

Let’s personalize NGINX.

Open:

src/core/nginx.c

Find this line (around line 415):

Image description

"nginx version: nginx/1.21.3"

Change it to:

"nginx version: nginx by Muhammed Sirajudeen"

Image description

Save it.


Step 9: Rebuild and Run

make
cd objs
./nginx -h

You should see your personalized version string!

Image description


Conclusion

You didn’t just build NGINX — you understood how it detects system features, how the build system is orchestrated, and how to make it your own. That’s what truly mastering a tool looks like.

Want to go further? Try adding a custom directive to NGINX, or explore how modules are loaded.

Happy hacking — and keep exploring.