Building Nginx modules using CMake
Introduction
Modules in Nginx can be either dynamic or static. A dynamic module is a shared object that is loaded by Nginx at runtime and Nginx need to be instructed in the configuration to load the module.
A static module is compiled into the Nginx binary and, hence, does not need to be specified in Nginx configuration.
With dynamic modules you can usually use the Nginx version provided by the package manager of your operating system so you only build the module and update the configuration.
With a static module you will need to build your own Nginx binary.
This note will describe how to use CMake to build Nginx modules. This hello world nginx module has been used for testing.
Determine Nginx build options
When building a dynamically loadable module one will need the Nginx source code. The Nginx source code used when building the module need to have the same version as the Nginx binary where the module should be loaded.
Check the version and configure arguments using command (output is an example from Nginx in homebrew for OS X):
$ nginx -V
nginx version: nginx/1.23.2
built by clang 14.0.0 (clang-1400.0.29.202)
built with OpenSSL 1.1.1q 5 Jul 2022 (running with OpenSSL 1.1.1s 1 Nov 2022)
TLS SNI support enabled
configure arguments: --prefix=/opt/homebrew/Cellar/nginx/1.23.2 --sbin-path=/opt/homebrew/Cellar/nginx/1.23.2/bin/nginx --with-cc-opt='-I/opt/homebrew/opt/pcre2/include -I/opt/homebrew/opt/openssl@1.1/include' --with-ld-opt='-L/opt/homebrew/opt/pcre2/lib -L/opt/homebrew/opt/openssl@1.1/lib' --conf-path=/opt/homebrew/etc/nginx/nginx.conf --pid-path=/opt/homebrew/var/run/nginx.pid --lock-path=/opt/homebrew/var/run/nginx.lock --http-client-body-temp-path=/opt/homebrew/var/run/nginx/client_body_temp --http-proxy-temp-path=/opt/homebrew/var/run/nginx/proxy_temp --http-fastcgi-temp-path=/opt/homebrew/var/run/nginx/fastcgi_temp --http-uwsgi-temp-path=/opt/homebrew/var/run/nginx/uwsgi_temp --http-scgi-temp-path=/opt/homebrew/var/run/nginx/scgi_temp --http-log-path=/opt/homebrew/var/log/nginx/access.log --error-log-path=/opt/homebrew/var/log/nginx/error.log --with-compat --with-debug --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_degradation_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-ipv6 --with-mail --with-mail_ssl_module --with-pcre --with-pcre-jit --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module
Make sure that the --with-compat
options is there. If not, you may need to
use the exact same config options when building the module.
Build a dynamic module
Using a CMake external project is convenient because then source code can be downloaded, configured and built using any command.
Create a CMakeLists.txt
file in the module directory and put mandatory
directives in the top of the file
cmake_minimum_required(VERSION 3.24)
project(nginx-module)
then create an external project for Nginx like this:
include(ExternalProject)
ExternalProject_Add(
nginx
DOWNLOAD_COMMAND URL https://nginx.org/download/nginx-1.23.2.tar.gz
CONFIGURE_COMMAND cd ${CMAKE_CURRENT_BINARY_DIR}/nginx-prefix/src/nginx && ./configure --add-dynamic-module=${CMAKE_BINARY_DIR}/../ --with-compat
BUILD_COMMAND make -C ${CMAKE_CURRENT_BINARY_DIR}/nginx-prefix/src/nginx modules
INSTALL_COMMAND ""
)
As seen the configure command uses --add-dynamic-module
to point out the
directory of the module. When using CMake a separate subdirectory is used for
doing the build in the nginx module directory, that is why we use
${CMAKE_BINARY_DIR}/..
.
Make sure to use the same version of Nginx as where you will load the module. If not, Nginx will fail to start and show an error when loading the module. This is an example when Nginx 1.23.2 is loading a module build usinh Nginx 1.18 source code:
nginx: [emerg] module "/opt/homebrew/Cellar/nginx/1.23.2/modules/ngx_http_hello_world_module.so" version 1018000 instead of 1023002 in /opt/homebrew/etc/nginx/nginx.conf:11
You can also add a custom command to copy the module shared object into a
build output directory to the CMakeLists.txt
-file:
add_custom_command(
TARGET nginx
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_SOURCE_DIR}/output
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/nginx-prefix/src/nginx/objs/*.so ${CMAKE_SOURCE_DIR}/output/
COMMENT "Copy nginx module to ${CMAKE_SOURCE_DIR}/output/"
)
Then build the module
$ cmake -B build
$ cmake --build build --parallel 8
The module will be found in the output
directory.
Load the dynamic module
From the configuration options (seen in nignx -V
) you can see from what
location Nginx is loading its modules in the option --modules-path
. If this
option is not specified, the modules are loaded from the modules
directory
under the path specified in --prefix
. In the example above the modules will be
loaded from /opt/homebrew/Cellar/nginx/1.23.2/modules
Copy the module to the modules directory. Then in the configuration add
load_module "modules/ngx_hello_world_module.so";
Then start Nginx or reload config
$ nginx -s reload
To test the hello world module just add a test location to the config which is using the directive defined in the module
location /test {
hello_world;
}
Then reload the config and curl the new location:
$ nginx -s reload
$ curl localhost/test
hello world
Build a static module
Building a static module means that the module is built into the Nginx binary, so you basically build Nginx and specify a path to the module.
Add this CMakeLists.txt into your module directory to build Nginx containing the static module
ExternalProject_Add(
nginx
DOWNLOAD_COMMAND URL https://nginx.org/download/nginx-1.18.0.tar.gz
CONFIGURE_COMMAND cd ${CMAKE_CURRENT_BINARY_DIR}/nginx-prefix/src/nginx && ./configure --add-module=${CMAKE_BINARY_DIR}/../ --with-compat --prefix=/opt/nginx
BUILD_COMMAND make -C ${CMAKE_CURRENT_BINARY_DIR}/nginx-prefix/src/nginx
INSTALL_COMMAND make -C ${CMAKE_CURRENT_BINARY_DIR}/nginx-prefix/src/nginx install
)
Build with:
$ cmake -B build
$ cmake --build build --parallel 8
The resulting build can be found in /opt/nginx
To test the hello world module just add a test location using the directive
defined in the module. Note that you don’t need load_module
since the module
is already in the Nginx binary.
location /test {
hello_world;
}
Then reload the config and curl
$ nginx -s reload
$ curl localhost/test
hello world
References
Compiling dynamic modules for nginx