# ngx_echo gives Nginx a printf, a sleep and a subrequest in the config file

> The OpenResty echo module adds echo, sleep, timers, shell style execution and parallel or chained subrequests to the Nginx configuration language. Its stated purpose is testing and debugging other modules by emulating faked subrequest locations, and its secondary use is serving static content straight out of the configuration file or wrapping a proxied response with a header and footer.

**openresty/echo-nginx-module** — An Nginx module for bringing the power of "echo", "sleep", "time" and more to Nginx's config file

- Repository: https://github.com/openresty/echo-nginx-module
- Website: http://wiki.nginx.org/NginxHttpEchoModule
- Stars: 1,194 · Forks: 250
- Language: C
- License: BSD-2-Clause
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/openresty-echo-nginx-module

## Shell style directives in the configuration language

The module is called ngx_echo, and its one line summary is that it brings echo, sleep, time, exec and more shell style goodies to the Nginx config file. The smallest possible example is a location that prints a string.

```nginx
   location /hello {
     echo "hello, world!";
   }
```

The directives behave the way the names suggest. echo prints, echo_flush makes sure the client can see the previous output immediately, echo_sleep takes a delay in seconds, echo_reset_timer restarts the timer that $echo_timer_elapsed reads, and echo_duplicate repeats a string a given number of times. The last one has an obvious use in a test, and the README gives an example that generates a billion copies of a string.

```nginx
   # /bighello will generate 1000,000,000 hello's.
   location /bighello {
     echo_duplicate 1000_000_000 'hello';
   }
```

The module describes itself as wrapping lots of Nginx internal APIs for streaming input and output, parallel and sequential subrequests, timers and sleeping, and various metadata access. That list is the real feature set, and the echo directive is only the visible part of it.

The status is stated plainly at the top of the document: this module is production ready. The version section says the document describes ngx_echo v0.63, released on 1 August 2022, and the project publishes no GitHub releases, so the tags are the version reference.

## Parallel and chained subrequests are the interesting part

The subrequest directives are what separate this module from a printf. echo_location runs a subrequest and echo_location_async starts one and does not wait for it, and the two together let you fan out to several locations and stitch the results together.

The README's example contrasts the two. With echo_location_async for /sub1 and /sub2, where the first sleeps two seconds and the second sleeps one second, the output arrives in about two seconds in total, because the requests run in parallel.

```nginx
   location /main {
       echo_reset_timer;

       # subrequests in parallel
       echo_location_async /sub1;
       echo_location_async /sub2;

       echo "took $echo_timer_elapsed sec for total.";
   }
```

With echo_location instead, the same two subrequests are chained by continuation passing, and the total is about three seconds, because the second does not start until the first has finished.

```nginx
   location /main {
       echo_reset_timer;

       # subrequests in series (chained by CPS)
       echo_location /sub1;
       echo_location /sub2;

       echo "took $echo_timer_elapsed sec for total.";
   }
```

That difference is the module's reason for existing in a testing context. When you are writing another Nginx module and you need to see how it behaves when a request fans out to several backends, or when each one is slow, you need somewhere to point it. The README says the module provides utilities that help testing and debugging of other modules by trivially emulating different kinds of faked subrequest locations, and the parallel and serial pair is exactly that kind of fake.

The merging example extends the idea further. echo_foreach_split splits a query string on a separator and iterates the pieces, and $echo_it holds the current item, so a location can fetch several resources named in the query string and concatenate them into one response, as the README does with a set of JavaScript files.

## Before and after body filters wrap a proxied response

The two filter directives are echo_before_body and echo_after_body, and they let a location emit text before and after the body that comes from somewhere else. The README's example proxies to another location and wraps its output.

```nginx
   # in the following example, accessing /echo yields
   #   hello
   #   world
   #   blah
   #   hiya
   #   igor
   location /echo {
       echo_before_body hello;
       echo_before_body world;
       proxy_pass $scheme://127.0.0.1:$server_port$request_uri/more;
       echo_after_body hiya;
       echo_after_body igor;
   }
   location /echo/more {
       echo blah;
   }
```

That is the shape of the module's second real use case, which the Description section states directly. It is useful in real-world applications that need to serve static contents directly from memory, loading from the Nginx config file, and to wrap the upstream response with a custom header and footer, which is described as being like the addition module but with the contents read directly from the config file and Nginx variables.

The distinction from the addition module matters because of where the text comes from: this module reads the contents directly from the config file and Nginx variables, evaluated at request time.

The same mechanism works for making a request against yourself. echo_subrequest and echo_subrequest_async issue an internal subrequest, and the README shows a location that makes two of them in parallel with different methods, query strings and bodies, and prints the result of each with the query string, the method, the body and the content length of the subrequest.

## Variables expose the request, the timing and the response

The variable list is what makes the directives composable, and it falls into three groups.

The request group is $echo_request_body, $echo_request_method, $echo_request_uri and $echo_cacheable_request_uri, which is the variant used when a response is served from cache. The client group is $echo_client_request_method and $echo_client_request_headers, which give you the original request rather than the one the module constructed, so an echo back of the client's own headers is possible with a single directive.

```nginx
   # echo back the client request
   location /echoback {
     echo_duplicate 1 $echo_client_request_headers;
     echo "\r";

     echo_read_request_body;

     echo_request_body;
   }
```

The state group is $echo_timer_elapsed, which is the elapsed time since the last echo_reset_timer, $echo_incr for a counter, $echo_it for the current item in a foreach split, and $echo_response_status for the status of the response being produced. There is also echo_status, a content handler directive that outputs the status.

The timer deserves a note. The README's timed example resets the timer, prints a line, prints how long that line took, prints another line and prints its time, so the module is acting as a stopwatch inside the request. Combined with the subrequest directives, that gives you a way to see where the time in a request actually goes.

The remaining directives fill in the edges. echo_read_request_body reads the body of the current request so that $echo_request_body can be used, echo_request_body outputs it, echo_blocking_sleep blocks the worker rather than yielding, echo_end closes a foreach split, and echo_exec runs a shell style command.

## The module is not bundled with Nginx

The first note in the README is a distribution one, and it is the practical constraint for anyone evaluating this. This module is not distributed with the Nginx source, and the README points to its own installation instructions for that. The project page is the Nginx wiki at wiki.nginx.org/NginxHttpEchoModule, and the source is in this repository, with the usual OpenResty layout of a config/ directory for the module definition, src/ for the C code, t/ for the test suite, and util/ for helper scripts.

The module is built the way an Nginx module is built, and the OpenResty project publishes the instructions.

The compatibility and test sections are worth looking at before you commit to it. The README has a Compatibility section, a section on modules that use this module for testing, a test suite section, a TODO section and a Changes section, and it carries English and Chinese mailing list links, a report bugs section, a source repository section and a getting involved section, with the author and the copyright and licence section at the end.

The licence is BSD 2 clause, with the file at the repository root, and the last push was on 2026-09-16 with the project not archived. The version the document describes, v0.63, is from 2022, so the code is being touched while the documented version has not moved, which is a combination worth keeping in mind when you read the instructions and the directive reference together.

## Conclusion

ngx_echo fits a team building or testing Nginx modules who needs to fake a subrequest location, emit a sequence of responses, or measure how long a stage takes, and it is a cheap way to serve small static payloads from the config itself. It is the wrong tool for producing a whole application response, since a proxy_pass to a real backend is the normal path and this module only adds a response layer around it. Verify first that your Nginx build can load the module, since it is not distributed with the Nginx source, and read the status note in the README because the documented version is v0.63 from 2022.

## FAQ

### What does the ngx_echo module do?

It brings echo, sleep, time, exec and other shell style directives to the Nginx config file. The module wraps Nginx internal APIs for streaming input and output, parallel and sequential subrequests, timers and sleeping, and various metadata access.

### What is the difference between echo_location and echo_location_async?

echo_location runs a subrequest and chains the next one through continuation passing, so two subrequests that sleep two and one seconds take about three seconds in total. echo_location_async starts the subrequest without waiting, so the same pair takes about two seconds because they run in parallel.

### What are echo_before_body and echo_after_body for?

They emit text before and after the body produced elsewhere, so a location can wrap a proxied response with a header and footer. The README describes this as like the addition module, but with the contents read directly from the config file and Nginx variables.

### Is ngx_echo included with Nginx?

No. The README states that this module is not distributed with the Nginx source and points to the installation instructions, and the project page is the Nginx wiki at wiki.nginx.org/NginxHttpEchoModule. The module is described as production ready, and the documented version is v0.63 from 1 August 2022.

### How do I time a request with ngx_echo?

Call echo_reset_timer to start the clock and read $echo_timer_elapsed for the elapsed time since that reset. The README's example prints one line, prints how long it took, prints a second line and prints its own elapsed time.

## Sources

- [Issues](https://github.com/openresty/echo-nginx-module/issues)
- [License: BSD-2-Clause](https://github.com/openresty/echo-nginx-module/blob/master/LICENSE)
- [openresty/echo-nginx-module on GitHub](https://github.com/openresty/echo-nginx-module)
- [Project website](http://wiki.nginx.org/NginxHttpEchoModule)
- [README](https://github.com/openresty/echo-nginx-module/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/openresty-echo-nginx-module
