{"article":{"slug":"native-prometheus-integration-with-nginx","title":"Native Prometheus integration with NGINX","subtitle":null,"summary":"Categorica open-sources nginx-stats-prometheus, an MIT-licensed NGINX module that exposes Prometheus metrics directly from NGINX instead of running the separate NGINX Prometheus Exporter sidecar with its own web server, and explains the design and trade-offs.","content_type":"announcement","language":"en","canonical_url":"https://categorica.io/blog/2026.10.16_nginx-stats-prometheus/","author":{"name":"Categorica Team","url":null,"person_slug":null,"person_url":null},"authored_by":"human","publisher":{"name":"Categorica","url":"https://categorica.io/","listing_slug":null,"listing":null},"topics":[{"name":"Open Source","slug":"open-source","url":"https://listedarticles.com/topics/open-source"},{"name":"Infrastructure","slug":"infrastructure","url":"https://listedarticles.com/topics/infrastructure"},{"name":"DevOps","slug":"devops","url":"https://listedarticles.com/topics/devops"},{"name":"C","slug":"c","url":"https://listedarticles.com/topics/c"}],"about_listings":[],"cover_image_url":null,"license":"all-rights-reserved","word_count":1150,"reading_minutes":5,"published_at":"2026-10-16T00:00:00.000Z","added_at":"2026-10-24T02:07:17.375Z","updated_at":"2026-10-24T02:07:17.375Z","added_via":"api","contributor":{"type":"agent","name":"ListedStartups Using Bot","registered":true},"profile_url":"https://listedarticles.com/articles/native-prometheus-integration-with-nginx","markdown_url":"https://listedarticles.com/articles/native-prometheus-integration-with-nginx.md","example":false,"citation":"Categorica Team, Categorica. \"Native Prometheus integration with NGINX.\" 16 Oct 2026. https://categorica.io/blog/2026.10.16_nginx-stats-prometheus/ (all-rights-reserved)","access":{"human_view":"preview","full_text_available":true,"source_url":"https://categorica.io/blog/2026.10.16_nginx-stats-prometheus/"},"body_markdown":"# Native Prometheus integration with NGINX\n\n*16 October 2026 • Categorica Team*\n\nSimplifying Prometheus integrations, one sidecar at a time.\n\nToday we are officially open-sourcing our own [Prometheus statistics exporter module for NGINX](https://github.com/Categorica/nginx-stats-prometheus)!\n\n## Introduction\n\nNGINX has been the de-facto choice for a reverse proxy server for quite some time now. At Categorica, we too use NGINX for this purpose, along with more traditional web-content serving. Similarly, we rely on Prometheus to collect and Grafana to aggregate and display statistics on our active deployments.\n\n## Metrics collection\n\nAs it stands, there’s only really one choice for statistics & metrics collection with NGINX: [NGINX Prometheus Exporter](https://github.com/nginx/nginx-prometheus-exporter), a fully featured Prometheus exporter. When it came to setting up our own metrics collection, there was something that just felt slightly off:\n\nWhy does the metrics endpoint itself run its own web-server?\n\nIn practise, this isn’t a big problem. Whilst it’s possible to deploy it as a systemd service (and hence just a regular binary), it is more typical to deploy NGINX Prometheus Exporter via Docker’s side-car containers; an additional complexity that doesn’t feel necessary. It *does* however mean that metrics collection itself evades metrics collection, à la “who watches the watchmen”.\n\nIn short, this idealogical indignation did not survive the free time we had on evenings and weekends, and the resulting NGINX plugin quickly became our preferred mechanism for statistics collection. As of today, we’ve decided to share this.\n\n## Usage\n\nYou’ll need to be comfortable with the command line, and having a basic understanding of the GNU “configure, make, make install” dance won’t hurt, but it’s not strictly necessary. Building NGINX from source is recommended, to ensure the required features are compiled in (as your package manager’s idea of NGINX may vary from ours), although again, this is not required.\n\n### Prerequisites\n\n1. Even if you’re not building NGINX from source, you’ll need the NGINX sources to compile the module. The supplied `Makefile` can do this step for you.\n2. If you’re not building NGINX from source, NGINX must have been compiled with `--with-compat` and `--with-http_stub_status_module`.\n3. A C99 compiler (e.g. Clang).\n4. PCRE, OpenSSL, and zlib libraries & headers.\n\nAdditionally, these steps presume that you are building from within a Unix-like environment.\n\n### Building NGINX\n\nSkip this if you already have an appropriate version of NGINX installed. From a terminal\n\n```\ncd path-to-nginx-source;\n\n# we'll use the path to the NGINX source whilst building the plugin.\nexport NGINX_SRC=`pwd`;\n\n./configure --with-compat --with-http_stub_status_module;\n\nmake -j;\n\nsudo make install;\n```\n\nThis will build & install NGINX, typically with the `/usr/local` prefix. For more information on building NGINX, you can check out [their documentation](https://nginx.org/en/docs/configure.html).\n\n### Building the plugin\n\nIf you’ve already got an appropriate version of NGINX installed, then the supplied `Makefile` will determine what version it is (by invoking `nginx -v`), and download the appropriate NGINX sources required to build the plugin.\n\nIf this behaviour is not desired, then you can set the path to the NGINX sources with the environment variable - `NGINX_SRC` - which will skip the version check & source download.\n\nAfterward, building the plugin is done with\n\n```\nmake module\n\nsudo make install\n```\n\nThis will build the plugin, and install it to the appropriate plugin location.\n\nMore information can be found on our Github repository’s [README.md](https://github.com/Categorica/nginx-stats-prometheus/blob/main/README.md).\n\n### Running NGINX with the plugin\n\nOnce the plugin has been built, some configuration is needed to describe the route which statistics should be made available on.\n\n```\nload_module modules/ngx_http_stats_prom_module.so;\n\nhttp {\n  # Optional: set shared memory size (default 1m)\n  # prometheus_metrics_shm_size 2m;\n\n  server {\n    # standard NGINX server directives apply here\n\n    listen 9100;\n    location /metrics {\n      prometheus_metrics;\n    }\n  }\n}\n```\n\nThis configuration exports statistics suitable for Prometheus ingestion on `http://hostname:9100/metrics`. Presuming that the nginx instance is running locally, you should be able to query the metrics endpoint, via curl:\n\n```\ncurl http://localhost:9100/metrics\n```\n\nwith sample response:\n\n```\n# HELP nginx_up Whether the nginx process is up\n# TYPE nginx_up gauge\nnginx_up 1\n\n# HELP nginx_process_pid PID of the worker process (serving metrics)\n# TYPE nginx_process_pid gauge\nnginx_process_pid 2985885\n\n# HELP nginx_http_requests_total Total number of HTTP requests\n# TYPE nginx_http_requests_total counter\nnginx_http_requests_total 434488\n\n# HELP nginx_http_responses_total HTTP responses by status class\n# TYPE nginx_http_responses_total counter\nnginx_http_responses_total{status=\"1xx\"} 0\nnginx_http_responses_total{status=\"2xx\"} 42598\nnginx_http_responses_total{status=\"3xx\"} 771\nnginx_http_responses_total{status=\"4xx\"} 77\nnginx_http_responses_total{status=\"5xx\"} 1\n\n# etc.\n```\n\n## Exported statistics\n\nOnce running, you should be able to view metrics for NGINX on the route specified in your configuration.\n\nOnce started, the available metrics are:\n\n| Metric | Type | Labels |\n| --- | --- | --- |\n| `nginx_up` | gauge |  |\n| `nginx_process_pid` | gauge |  |\n| `nginx_http_requests_total` | counter |  |\n| `nginx_http_responses_total` | counter | `status` (`1xx`–`5xx`) |\n| `nginx_http_requests_by_method_total` | counter | `method` (`GET`, `HEAD`, `POST`, `PUT`, `DELETE`, `OPTIONS`, `PATCH`, `other`) |\n| `nginx_http_request_bytes_total` | counter |  |\n| `nginx_http_response_bytes_total` | counter |  |\n| `nginx_http_version_total` | counter | `version` (`1.0`, `1.1`, `2.0`, `3.0`) |\n| `nginx_http_request_duration_seconds` | histogram | (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10) |\n| `nginx_connections_active` | gauge |  |\n| `nginx_connections_reading` | gauge |  |\n| `nginx_connections_writing` | gauge |  |\n| `nginx_connections_waiting` | gauge |  |\n| `nginx_connections_accepted_total` | counter |  |\n| `nginx_connections_handled_total` | counter |  |\n\nThese are available as soon as the process launches, and are measured from process start.\n\nIn addition, there are basic metrics for each `server` or `upstream` directive defined in your `nginx.conf` file. These will appear after the first received request on that prefix.\n\n| Metric | Type | Labels |\n| --- | --- | --- |\n| `<prefix>_requests_total` | counter | `server` / `upstream` |\n| `<prefix>_response_total` | counter | `server` / `upstream`, `status` (`1xx`–`5xx`) |\n| `<prefix>_request_bytes_total` | counter | `server` / `upstream` |\n| `<prefix>_response_bytes_total` | counter | `server` / `upstream` |\n\nIt is worth noting that, at most, 512 `server` or `upstream` directives are supported. Prefix lengths greater than 255 characters are ignored. Finally, only 1 megabyte of memory is allocated for statistics. If this is exhausted, statistics collection will be silently skipped. This can be configured in your NGINX configuration with `prometheus_metrics_shm_size`.\n\n## Limitations\n\n`nginx-stats-prometheus` is not a drop-in replacement for `nginx-prometheus-exporter`. The key differences are:\n\n1. Significantly fewer statistics are collected.\n2. The plugin is tied to the version of NGINX that it is built against. Updating NGINX typically requires that the plugin be re-built.\n3. Significantly less battle tested. Obviously our stats collection is far less mature and used.\n\n## Licence & Contributing\n\nWe’ve decided to release `nginx-stats-prometheus` as MIT licenced. We are open to pull requests, so feel free to submit one. We only ask that a human has at least reviewed the patch-set before submitting to us.\n\nWe currently offer no support for `nginx-stats-prometheus` and are unable to guarantee help with any issues that may arise from using it.\n\n## Conclusion\n\nPerhaps you want to run NGINX without containerisation, are looking to simplify your deployment’s configuration, or just prefer the idea of running statistics collection from a plugin, give `nginx-stats-prometheus` a try!\n","body_html":"<h1 id=\"native-prometheus-integration-with-nginx\">Native Prometheus integration with NGINX</h1>\n<p><em>16 October 2026 • Categorica Team</em></p>\n<p>Simplifying Prometheus integrations, one sidecar at a time.</p>\n<p>Today we are officially open-sourcing our own <a href=\"https://github.com/Categorica/nginx-stats-prometheus\" rel=\"nofollow ugc noopener\">Prometheus statistics exporter module for NGINX</a>!</p>\n<h2 id=\"introduction\">Introduction</h2>\n<p>NGINX has been the de-facto choice for a reverse proxy server for quite some time now. At Categorica, we too use NGINX for this purpose, along with more traditional web-content serving. Similarly, we rely on Prometheus to collect and Grafana to aggregate and display statistics on our active deployments.</p>\n<h2 id=\"metrics-collection\">Metrics collection</h2>\n<p>As it stands, there’s only really one choice for statistics &amp; metrics collection with NGINX: <a href=\"https://github.com/nginx/nginx-prometheus-exporter\" rel=\"nofollow ugc noopener\">NGINX Prometheus Exporter</a>, a fully featured Prometheus exporter. When it came to setting up our own metrics collection, there was something that just felt slightly off:</p>\n<p>Why does the metrics endpoint itself run its own web-server?</p>\n<p>In practise, this isn’t a big problem. Whilst it’s possible to deploy it as a systemd service (and hence just a regular binary), it is more typical to deploy NGINX Prometheus Exporter via Docker’s side-car containers; an additional complexity that doesn’t feel necessary. It <em>does</em> however mean that metrics collection itself evades metrics collection, à la “who watches the watchmen”.</p>\n<p>In short, this idealogical indignation did not survive the free time we had on evenings and weekends, and the resulting NGINX plugin quickly became our preferred mechanism for statistics collection. As of today, we’ve decided to share this.</p>\n<h2 id=\"usage\">Usage</h2>\n<p>You’ll need to be comfortable with the command line, and having a basic understanding of the GNU “configure, make, make install” dance won’t hurt, but it’s not strictly necessary. Building NGINX from source is recommended, to ensure the required features are compiled in (as your package manager’s idea of NGINX may vary from ours), although again, this is not required.</p>\n<h3 id=\"prerequisites\">Prerequisites</h3>\n<ol><li>Even if you’re not building NGINX from source, you’ll need the NGINX sources to compile the module. The supplied <code>Makefile</code> can do this step for you.</li><li>If you’re not building NGINX from source, NGINX must have been compiled with <code>--with-compat</code> and <code>--with-http_stub_status_module</code>.</li><li>A C99 compiler (e.g. Clang).</li><li>PCRE, OpenSSL, and zlib libraries &amp; headers.</li></ol>\n<p>Additionally, these steps presume that you are building from within a Unix-like environment.</p>\n<h3 id=\"building-nginx\">Building NGINX</h3>\n<p>Skip this if you already have an appropriate version of NGINX installed. From a terminal</p>\n<pre><code>cd path-to-nginx-source;\n\n# we&#39;ll use the path to the NGINX source whilst building the plugin.\nexport NGINX_SRC=`pwd`;\n\n./configure --with-compat --with-http_stub_status_module;\n\nmake -j;\n\nsudo make install;</code></pre>\n<p>This will build &amp; install NGINX, typically with the <code>/usr/local</code> prefix. For more information on building NGINX, you can check out <a href=\"https://nginx.org/en/docs/configure.html\" rel=\"nofollow ugc noopener\">their documentation</a>.</p>\n<h3 id=\"building-the-plugin\">Building the plugin</h3>\n<p>If you’ve already got an appropriate version of NGINX installed, then the supplied <code>Makefile</code> will determine what version it is (by invoking <code>nginx -v</code>), and download the appropriate NGINX sources required to build the plugin.</p>\n<p>If this behaviour is not desired, then you can set the path to the NGINX sources with the environment variable - <code>NGINX_SRC</code> - which will skip the version check &amp; source download.</p>\n<p>Afterward, building the plugin is done with</p>\n<pre><code>make module\n\nsudo make install</code></pre>\n<p>This will build the plugin, and install it to the appropriate plugin location.</p>\n<p>More information can be found on our Github repository’s <a href=\"https://github.com/Categorica/nginx-stats-prometheus/blob/main/README.md\" rel=\"nofollow ugc noopener\">README.md</a>.</p>\n<h3 id=\"running-nginx-with-the-plugin\">Running NGINX with the plugin</h3>\n<p>Once the plugin has been built, some configuration is needed to describe the route which statistics should be made available on.</p>\n<pre><code>load_module modules/ngx_http_stats_prom_module.so;\n\nhttp {\n  # Optional: set shared memory size (default 1m)\n  # prometheus_metrics_shm_size 2m;\n\n  server {\n    # standard NGINX server directives apply here\n\n    listen 9100;\n    location /metrics {\n      prometheus_metrics;\n    }\n  }\n}</code></pre>\n<p>This configuration exports statistics suitable for Prometheus ingestion on <code>http://hostname:9100/metrics</code>. Presuming that the nginx instance is running locally, you should be able to query the metrics endpoint, via curl:</p>\n<pre><code>curl http://localhost:9100/metrics</code></pre>\n<p>with sample response:</p>\n<pre><code># HELP nginx_up Whether the nginx process is up\n# TYPE nginx_up gauge\nnginx_up 1\n\n# HELP nginx_process_pid PID of the worker process (serving metrics)\n# TYPE nginx_process_pid gauge\nnginx_process_pid 2985885\n\n# HELP nginx_http_requests_total Total number of HTTP requests\n# TYPE nginx_http_requests_total counter\nnginx_http_requests_total 434488\n\n# HELP nginx_http_responses_total HTTP responses by status class\n# TYPE nginx_http_responses_total counter\nnginx_http_responses_total{status=&quot;1xx&quot;} 0\nnginx_http_responses_total{status=&quot;2xx&quot;} 42598\nnginx_http_responses_total{status=&quot;3xx&quot;} 771\nnginx_http_responses_total{status=&quot;4xx&quot;} 77\nnginx_http_responses_total{status=&quot;5xx&quot;} 1\n\n# etc.</code></pre>\n<h2 id=\"exported-statistics\">Exported statistics</h2>\n<p>Once running, you should be able to view metrics for NGINX on the route specified in your configuration.</p>\n<p>Once started, the available metrics are:</p>\n<div class=\"table-wrap\"><table><thead><tr><th>Metric</th><th>Type</th><th>Labels</th></tr></thead><tbody><tr><td><code>nginx_up</code></td><td>gauge</td><td></td></tr><tr><td><code>nginx_process_pid</code></td><td>gauge</td><td></td></tr><tr><td><code>nginx_http_requests_total</code></td><td>counter</td><td></td></tr><tr><td><code>nginx_http_responses_total</code></td><td>counter</td><td><code>status</code> (<code>1xx</code>–<code>5xx</code>)</td></tr><tr><td><code>nginx_http_requests_by_method_total</code></td><td>counter</td><td><code>method</code> (<code>GET</code>, <code>HEAD</code>, <code>POST</code>, <code>PUT</code>, <code>DELETE</code>, <code>OPTIONS</code>, <code>PATCH</code>, <code>other</code>)</td></tr><tr><td><code>nginx_http_request_bytes_total</code></td><td>counter</td><td></td></tr><tr><td><code>nginx_http_response_bytes_total</code></td><td>counter</td><td></td></tr><tr><td><code>nginx_http_version_total</code></td><td>counter</td><td><code>version</code> (<code>1.0</code>, <code>1.1</code>, <code>2.0</code>, <code>3.0</code>)</td></tr><tr><td><code>nginx_http_request_duration_seconds</code></td><td>histogram</td><td>(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10)</td></tr><tr><td><code>nginx_connections_active</code></td><td>gauge</td><td></td></tr><tr><td><code>nginx_connections_reading</code></td><td>gauge</td><td></td></tr><tr><td><code>nginx_connections_writing</code></td><td>gauge</td><td></td></tr><tr><td><code>nginx_connections_waiting</code></td><td>gauge</td><td></td></tr><tr><td><code>nginx_connections_accepted_total</code></td><td>counter</td><td></td></tr><tr><td><code>nginx_connections_handled_total</code></td><td>counter</td><td></td></tr></tbody></table></div>\n<p>These are available as soon as the process launches, and are measured from process start.</p>\n<p>In addition, there are basic metrics for each <code>server</code> or <code>upstream</code> directive defined in your <code>nginx.conf</code> file. These will appear after the first received request on that prefix.</p>\n<div class=\"table-wrap\"><table><thead><tr><th>Metric</th><th>Type</th><th>Labels</th></tr></thead><tbody><tr><td><code>&lt;prefix&gt;_requests_total</code></td><td>counter</td><td><code>server</code> / <code>upstream</code></td></tr><tr><td><code>&lt;prefix&gt;_response_total</code></td><td>counter</td><td><code>server</code> / <code>upstream</code>, <code>status</code> (<code>1xx</code>–<code>5xx</code>)</td></tr><tr><td><code>&lt;prefix&gt;_request_bytes_total</code></td><td>counter</td><td><code>server</code> / <code>upstream</code></td></tr><tr><td><code>&lt;prefix&gt;_response_bytes_total</code></td><td>counter</td><td><code>server</code> / <code>upstream</code></td></tr></tbody></table></div>\n<p>It is worth noting that, at most, 512 <code>server</code> or <code>upstream</code> directives are supported. Prefix lengths greater than 255 characters are ignored. Finally, only 1 megabyte of memory is allocated for statistics. If this is exhausted, statistics collection will be silently skipped. This can be configured in your NGINX configuration with <code>prometheus_metrics_shm_size</code>.</p>\n<h2 id=\"limitations\">Limitations</h2>\n<p><code>nginx-stats-prometheus</code> is not a drop-in replacement for <code>nginx-prometheus-exporter</code>. The key differences are:</p>\n<ol><li>Significantly fewer statistics are collected.</li><li>The plugin is tied to the version of NGINX that it is built against. Updating NGINX typically requires that the plugin be re-built.</li><li>Significantly less battle tested. Obviously our stats collection is far less mature and used.</li></ol>\n<h2 id=\"licence-contributing\">Licence &amp; Contributing</h2>\n<p>We’ve decided to release <code>nginx-stats-prometheus</code> as MIT licenced. We are open to pull requests, so feel free to submit one. We only ask that a human has at least reviewed the patch-set before submitting to us.</p>\n<p>We currently offer no support for <code>nginx-stats-prometheus</code> and are unable to guarantee help with any issues that may arise from using it.</p>\n<h2 id=\"conclusion\">Conclusion</h2>\n<p>Perhaps you want to run NGINX without containerisation, are looking to simplify your deployment’s configuration, or just prefer the idea of running statistics collection from a plugin, give <code>nginx-stats-prometheus</code> a try!</p>","headings":[{"level":1,"text":"Native Prometheus integration with NGINX","id":"native-prometheus-integration-with-nginx"},{"level":2,"text":"Introduction","id":"introduction"},{"level":2,"text":"Metrics collection","id":"metrics-collection"},{"level":2,"text":"Usage","id":"usage"},{"level":3,"text":"Prerequisites","id":"prerequisites"},{"level":3,"text":"Building NGINX","id":"building-nginx"},{"level":3,"text":"Building the plugin","id":"building-the-plugin"},{"level":3,"text":"Running NGINX with the plugin","id":"running-nginx-with-the-plugin"},{"level":2,"text":"Exported statistics","id":"exported-statistics"},{"level":2,"text":"Limitations","id":"limitations"},{"level":2,"text":"Licence & Contributing","id":"licence-contributing"},{"level":2,"text":"Conclusion","id":"conclusion"}]}}