|
| 1 | +--- |
| 2 | +description: HTTP traffic tunneling via the HTTP CONNECT method, enabling centralized egress control through a trusted NGINX Plus server. |
| 3 | +nd-docs: DOCS-441 |
| 4 | +title: HTTP CONNECT forward proxy |
| 5 | +toc: true |
| 6 | +weight: 600 |
| 7 | +type: |
| 8 | +- how-to |
| 9 | +--- |
| 10 | + |
| 11 | +In corporate networks, NGINX Plus R36 and later can be configured as a forward proxy server, facilitating client access to external resources. A forward proxy operates between internal clients and the global network, enabling centralized traffic control. |
| 12 | + |
| 13 | +Unlike a reverse proxy that protects servers, upstreams, or services, forward proxy serves client requests and regulates their access to the external resources. To enable this functionality, the HTTP `CONNECT` method is used to establish a secure tunnel between the client and the proxy server (NGINX Plus). This tunnel permits the transmission of HTTPS traffic and other protocols, such as SSH or FTP, through the proxy. |
| 14 | + |
| 15 | +## Enabling HTTP CONNECT proxy |
| 16 | + |
| 17 | +To enable the HTTP `CONNECT` forward proxy, add the [`tunnel_pass`](https://nginx.org/en/docs/http/ngx_http_tunnel_module.html#tunnel_pass) directive that turns on the forward proxy functionality within the `server` or `location` blocks. |
| 18 | + |
| 19 | +The directive can be used without any parameters. The default value is [`$host`](https://nginx.org/en/docs/http/ngx_http_core_module.html#var_host):[`$request_port`](https://nginx.org/en/docs/http/ngx_http_core_module.html#var_request_port) variables: all requests are automatically forwarded to external resources based on the host and the port which we are trying to access. |
| 20 | + |
| 21 | +The `tunnel_pass` directive is a content handling directive (similar to `proxy_pass` or `grpc_pass`), and if specified for the `server` block, will not be inherited by corresponding `location` blocks. If you have a location that implements any other NGINX Plus capabilities within the forward proxy, you will need to enable the `tunnel_pass` for this `location` as well. See [Disabling other http methods](#disable-other-http-methods). |
| 22 | + |
| 23 | +```nginx |
| 24 | +server { |
| 25 | + listen 10.10.1.11:3128; |
| 26 | +
|
| 27 | + tunnel_pass; |
| 28 | +} |
| 29 | +``` |
| 30 | + |
| 31 | +In this basic configuration, the client establishes a tunnel with NGINX Plus, sends the HTTP `CONNECT` method to the specified port `3128` which is standard for forward proxying, and if successful, responds with `200 OK`. The tunnel is established and the client can perform the TLS handshake and get the response. |
| 32 | + |
| 33 | + |
| 34 | +## Disable other HTTP methods |
| 35 | + |
| 36 | +As soon as the `CONNECT` method is enabled, all other methods such as `GET`, `POST` are not applicable in this mode. Rejecting them ensures that only tunnel requests are allowed thus improving security. |
| 37 | + |
| 38 | +You can create a rule with the [`if`](https://nginx.org/en/docs/http/ngx_http_rewrite_module.html#if) directive to deny all HTTP methods except CONNECT, If a client uses any other method, NGINX Plus returns the `403` error code: |
| 39 | + |
| 40 | +```nginx |
| 41 | +server { |
| 42 | + listen 10.10.1.11:3128; |
| 43 | +
|
| 44 | + # Handle other methods |
| 45 | + location / { |
| 46 | + if ($request_method != CONNECT) { |
| 47 | + return 403 "Forbidden: allows only CONNECT method"; |
| 48 | + } |
| 49 | +
|
| 50 | + # allow CONNECT requests |
| 51 | + tunnel_pass; |
| 52 | + } |
| 53 | +} |
| 54 | +``` |
| 55 | + |
| 56 | +Note that in the example the [`tunnel_pass`](https://nginx.org/en/docs/http/ngx_http_tunnel_module.html#tunnel_pass) directive is specified inside the `location` block. Since `tunnel_pass` is a content handling directive, when it is specified for the `server` block, it is not inherited by nested locations. |
| 57 | + |
| 58 | + |
| 59 | +## Enable logging |
| 60 | + |
| 61 | +For testing, debugging and monitoring purposes you can configure error and access logs. |
| 62 | + |
| 63 | +1. At the `http` level, specify the [`log_format`](https://nginx.org/en/docs/http/ngx_http_log_module.html#log_format) directive, give the log a name (for example, `http_connect`), and include the metrics: |
| 64 | + |
| 65 | + ```nginx |
| 66 | + log_format http_connect '$remote_addr [$time_local] ' |
| 67 | + '$request_method $host:$request_port $status ' |
| 68 | + '$upstream_addr $bytes_sent $upstream_connect_time ' |
| 69 | + '$request_time "$http_user_agent"'; |
| 70 | + ``` |
| 71 | + |
| 72 | + See the [NGINX variables index](https://nginx.org/en/docs/varindex.html) for the list of all supported variables. |
| 73 | + |
| 74 | +2. Specify the [`error_log`](https://nginx.org/en/docs/ngx_core_module.html#error_log) and [`access_log`](https://nginx.org/en/docs/http/ngx_http_log_module.html#access_log) directives on the same configuration level where the [`tunnel_pass`](https://nginx.org/en/docs/http/ngx_http_tunnel_module.html#tunnel_pass) directive is defined: |
| 75 | + |
| 76 | + ```nginx |
| 77 | + access_log logs/connect_access.log http_connect; |
| 78 | + error_log logs/connect_error.log; |
| 79 | + ``` |
| 80 | + |
| 81 | +3. Test the configuration. This example enables logging for the location block which denies all HTTP methods except `CONNECT` and where the `tunnel_pass` directive is specified: |
| 82 | + |
| 83 | + ```nginx |
| 84 | + log_format http_connect '$remote_addr [$time_local] ' |
| 85 | + '$request_method $host:$request_port $status ' |
| 86 | + '$upstream_addr $bytes_sent $upstream_connect_time ' |
| 87 | + '$request_time "$http_user_agent"'; |
| 88 | + server { |
| 89 | + listen 10.10.1.11:3128; |
| 90 | +
|
| 91 | + location / { |
| 92 | + if ($request_method != CONNECT) { |
| 93 | + return 403 "Forbidden: allows only CONNECT method"; |
| 94 | + } |
| 95 | +
|
| 96 | + tunnel_pass; |
| 97 | +
|
| 98 | + access_log logs/connect_access.log http_connect; |
| 99 | + error_log logs/connect_error.log; |
| 100 | + } |
| 101 | + } |
| 102 | + ``` |
| 103 | + |
| 104 | + A test request can be sent with the `curl` command: |
| 105 | + |
| 106 | + ```shell |
| 107 | + curl -v -x 10.10.1.11:3128 https://example.com |
| 108 | + ``` |
| 109 | + The result of the command can be seen in the log (`200 OK`): |
| 110 | + |
| 111 | + ```none |
| 112 | + logs > connect_access.log |
| 113 | + 1 10.10.1.240 [01/Dec/2025:10:46:27 +0000] CONNECT example.com:443 200 127.456.789.0:443 4275 0.014 0.258 "curl/8.7.1" |
| 114 | + ``` |
| 115 | + |
| 116 | +## Access control |
| 117 | + |
| 118 | +It is highly recommended to restrict access to the proxy servers. Access control can be managed in several ways: |
| 119 | + |
| 120 | +- by ports and port ranges with the [`num_map`](https://nginx.org/en/docs/http/ngx_http_num_map_module.html) module (NGINX Plus R36) |
| 121 | +- by IP addresses with the [`geo`](https://nginx.org/en/docs/http/ngx_http_geo_module.html) module |
| 122 | +- by hostnames with the [`map`](https://nginx.org/en/docs/http/ngx_http_map_module.html) module |
| 123 | + |
| 124 | +All of these access methods can be used together and combined with the the [`proxy_allow_upstream`](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_allow_upstream) directive (NGINX Plus R36). |
| 125 | + |
| 126 | +### Restricting by ports and port ranges |
| 127 | + |
| 128 | +The [`num_map`](https://nginx.org/en/docs/http/ngx_http_num_map_module.html) module introduced in NGINX Plus R36 allows defining ports and port ranges and uses the same approach as the `map` block. For example, if the value of the [`$request_port`](https://nginx.org/en/docs/http/ngx_http_core_module.html#var_request_port) variable is `443`, the `$allowed_port` variable is assigned the value `ssl`. It also restricts the allowed port range for the upstream connections or proxy requests: |
| 129 | + |
| 130 | +```nginx |
| 131 | +num_map $request_port $allowed_port { |
| 132 | + default 0; |
| 133 | +
|
| 134 | + 443 ssl; |
| 135 | + <=1023 less-eq; |
| 136 | + 8080-8090 range; |
| 137 | + >8092 more; |
| 138 | +} |
| 139 | +``` |
| 140 | + |
| 141 | +### Restricting by IP addresses |
| 142 | + |
| 143 | +Access by IP addresses can be restricted with the [`geo`](https://nginx.org/en/docs/http/ngx_http_geo_module.html) module: |
| 144 | + |
| 145 | +```nginx |
| 146 | +geo $allowed_networks { |
| 147 | + 10.10.1.0/24 allow; |
| 148 | + 10.20.11.0/24 allow; |
| 149 | + default deny; |
| 150 | +} |
| 151 | +``` |
| 152 | + |
| 153 | +### Restricting by hostnames |
| 154 | + |
| 155 | +Restriction by hostnames can be configured with the [`map`](https://nginx.org/en/docs/http/ngx_http_map_module.html) module, which works mainly with strings and regular expressions. Although it is possible to match port numbers using `map`, it is recommended to use the [`num_map`](https://nginx.org/en/docs/http/ngx_http_num_map_module.html) module for more port-based restrictions, as it provides additional functionality such as defining ranges. |
| 156 | + |
| 157 | +In this example, access is allowed to `example.com` and its subdomains only: |
| 158 | + |
| 159 | +```nginx |
| 160 | +map $host $allowed_host { |
| 161 | + hostnames; |
| 162 | + default 0; |
| 163 | + example.com 1; |
| 164 | + *.example.com 1; |
| 165 | +} |
| 166 | +``` |
| 167 | + |
| 168 | +### Combining access control methods |
| 169 | + |
| 170 | +These access methods can be combined in the [`tunnel_allow_upstream`](https://nginx.org/en/docs/http/ngx_http_tunnel_module.html#tunnel_allow_upstream) directive, which performs access checks when the upstream server to which the request will be sent is selected. If any variable passed to [`tunnel_allow_upstream`](https://nginx.org/en/docs/http/ngx_http_tunnel_module.html#tunnel_allow_upstream) evaluates to non-zero or non-empty value, it will be calculated as `1` and access is granted. If all variables evaluate to `0` or an empty value, access is denied. |
| 171 | + |
| 172 | +### Access control example |
| 173 | + |
| 174 | +```nginx |
| 175 | +log_format connect_debug '$remote_addr [$time_local] ' |
| 176 | + '$request_method $host:$request_port $status ' |
| 177 | + '$upstream_addr $bytes_sent $upstream_connect_time ' |
| 178 | + '$request_time "$http_user_agent"'; |
| 179 | +
|
| 180 | +num_map $request_port $allowed_port { |
| 181 | + default 0; |
| 182 | +
|
| 183 | + 443 ssl; |
| 184 | + <=1023 less-eq; |
| 185 | + 8080-8090 range; |
| 186 | + >8092 more; |
| 187 | +} |
| 188 | +
|
| 189 | +geo $allowed_networks { |
| 190 | + 10.10.1.0/24 allow; |
| 191 | + 10.20.11.0/24 allow; |
| 192 | + default deny; |
| 193 | +} |
| 194 | +
|
| 195 | +map $host $allowed_host { |
| 196 | + hostnames; |
| 197 | + default 0; |
| 198 | + example.com 1; |
| 199 | + *.example.com 1; |
| 200 | +} |
| 201 | +
|
| 202 | +server { |
| 203 | + listen 10.10.1.11:3128; |
| 204 | +
|
| 205 | + #Allow CONNECT only to certain ports/nets/hosts |
| 206 | + tunnel_allow_upstream $allowed_networks $allowed_port $allowed_host; |
| 207 | +
|
| 208 | + error_page 403; |
| 209 | +
|
| 210 | + satisfy all; |
| 211 | +
|
| 212 | + allow 10.10.0.0/16; |
| 213 | + allow 127.0.0.1; |
| 214 | + deny all; |
| 215 | +
|
| 216 | + tunnel_pass; |
| 217 | +
|
| 218 | + location ^~ /errors/ { |
| 219 | + internal; |
| 220 | + root html; |
| 221 | + allow all; |
| 222 | + } |
| 223 | +
|
| 224 | + access_log logs/connect_access.log connect_debug; |
| 225 | + error_log logs/connect_debug.log debug; |
| 226 | +} |
| 227 | +``` |
| 228 | + |
| 229 | +Access can also be limited using other modules, for example with the [`satisfy`](https://nginx.org/en/docs/http/ngx_http_core_module.html#satisfy) directive, or the [`allow`/`deny`](https://nginx.org/en/docs/http/ngx_http_access_module.html#allow) directives. |
| 230 | + |
| 231 | + |
| 232 | +## mTLS authenticaton |
| 233 | + |
| 234 | +For proxy functionality, mutual mTLS authentication is supported: |
| 235 | + |
| 236 | +```nginx |
| 237 | +log_format http_connect '$remote_addr [$time_local] ' |
| 238 | + '$request_method $host:$request_port $status ' |
| 239 | + '$upstream_addr $bytes_sent $upstream_connect_time ' |
| 240 | + '$request_time "$http_user_agent"'; |
| 241 | +
|
| 242 | +
|
| 243 | +map $ssl_client_verify $ssl_ok { |
| 244 | + default 0; |
| 245 | + SUCCESS 1; |
| 246 | +} |
| 247 | +
|
| 248 | +map $host $allowed_host { |
| 249 | + hostnames; |
| 250 | + default 0; |
| 251 | + example.com 1; |
| 252 | + *.example.com 1; |
| 253 | +} |
| 254 | +
|
| 255 | +map "$ssl_ok:$allowed_host" $connect_ok { |
| 256 | + default 0; |
| 257 | + "1:0" 1; |
| 258 | + "1:1" 1; |
| 259 | + "0:1" 1; |
| 260 | +} |
| 261 | +
|
| 262 | +server { |
| 263 | + listen 10.10.1.11:3128; ssl; |
| 264 | +
|
| 265 | + ssl_certificate tls/proxy.cert; |
| 266 | + ssl_certificate_key tls/proxy.key; |
| 267 | +
|
| 268 | + ssl_client_certificate tls/ca.cert; |
| 269 | + ssl_verify_client on; |
| 270 | + ssl_verify_depth 1; |
| 271 | +
|
| 272 | + ssl_protocols TLSv1.2 TLSv1.3; |
| 273 | + ssl_prefer_server_ciphers on; |
| 274 | +
|
| 275 | + tunnel_allow_upstream $connect_ok; |
| 276 | +
|
| 277 | + tunnel_pass; |
| 278 | +
|
| 279 | + access_log logs/connect_access.log http_connect; |
| 280 | + error_log logs/connect_error.log; |
| 281 | +} |
| 282 | +``` |
| 283 | + |
0 commit comments