Skip to content

Enable PROXY procotol for proxy hosts and streams #3618

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ Immediately after logging in with this default user you will be asked to modify

All are welcome to create pull requests for this project, against the `develop` branch. Official releases are created from the `master` branch.

Run the following commands to test locally:

- `cd backend && npm i && node_modules/eslint/bin/eslint.js .`
- `cd test && npm i && npm run cypress`

CI is used in this project. All PR's must pass before being considered. After passing,
docker builds for PR's are available on dockerhub for manual verifications.

Expand Down
1 change: 1 addition & 0 deletions backend/internal/nginx.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ const internalNginx = {
let locationCopy = Object.assign({}, {access_list_id: host.access_list_id}, {certificate_id: host.certificate_id},
{ssl_forced: host.ssl_forced}, {caching_enabled: host.caching_enabled}, {block_exploits: host.block_exploits},
{allow_websocket_upgrade: host.allow_websocket_upgrade}, {http2_support: host.http2_support},
{enable_proxy_protocol: host.enable_proxy_protocol}, {load_balancer_ip: host.load_balancer_ip},
{hsts_enabled: host.hsts_enabled}, {hsts_subdomains: host.hsts_subdomains}, {access_list: host.access_list},
{certificate: host.certificate}, host.locations[i]);

Expand Down
41 changes: 41 additions & 0 deletions backend/migrations/20240310085523_proxy_protocol.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const migrate_name = 'proxy_protocol';
const logger = require('../logger').migrate;

/**
* Migrate
*
* @see http://knexjs.org/#Schema
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.up = function (knex/*, Promise*/) {
logger.info('[' + migrate_name + '] Migrating Up...');

return knex.schema.table('proxy_host', function (proxy_host) {
proxy_host.integer('enable_proxy_protocol').notNull().unsigned().defaultTo(0);
proxy_host.string('load_balancer_ip').notNull().defaultTo('');
})
.then(() => {
logger.info('[' + migrate_name + '] proxy_host Table altered');
});

};

/**
* Undo Migrate
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.down = function (knex/*, Promise*/) {
return knex.schema.table('proxy_host', function (proxy_host) {
proxy_host.dropColumn('enable_proxy_protocol');
proxy_host.dropColumn('load_balancer_ip');
})
.then(function () {
logger.info('[' + migrate_name + '] proxy_host Table altered');
});
};
41 changes: 41 additions & 0 deletions backend/migrations/20240310100432_proxy_protocol_streams.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const migrate_name = 'proxy_protocol_streams';
const logger = require('../logger').migrate;

/**
* Migrate
*
* @see http://knexjs.org/#Schema
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.up = function (knex/*, Promise*/) {
logger.info('[' + migrate_name + '] Migrating Up...');

return knex.schema.table('stream', function (stream) {
stream.integer('enable_proxy_protocol').notNull().unsigned().defaultTo(0);
stream.string('load_balancer_ip').notNull().defaultTo('');
})
.then(() => {
logger.info('[' + migrate_name + '] stream Table altered');
});

};

/**
* Undo Migrate
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.down = function (knex/*, Promise*/) {
return knex.schema.table('stream', function (stream) {
stream.dropColumn('enable_proxy_protocol');
stream.dropColumn('load_balancer_ip');
})
.then(function () {
logger.info('[' + migrate_name + '] stream Table altered');
});
};
31 changes: 20 additions & 11 deletions backend/templates/_listen.conf
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
listen 80;
{% if ipv6 -%}
listen [::]:80;
{% if enable_proxy_protocol == 1 or enable_proxy_protocol == true -%}
{% assign port_number_http = "88" -%}
{% assign port_number_https = "444" -%}
{% assign listen_extra_args = "proxy_protocol" -%}
{% else -%}
#listen [::]:80;
{% endif %}
{% assign port_number_http = "80" -%}
{% assign port_number_https = "443" -%}
{% assign listen_extra_args = "" -%}
{% endif -%}

listen {{ port_number_http }} {{ listen_extra_args }};
{% if ipv6 -%}
listen [::]:{{ port_number_http }} {{ listen_extra_args }};
{% endif -%}

{% if certificate -%}
listen 443 ssl{% if http2_support == 1 or http2_support == true %} http2{% endif %};
{% capture listen_extra_args_https %}ssl{% if http2_support %} http2{% endif %} {{ listen_extra_args }}{% endcapture -%}
listen {{ port_number_https }} {{ listen_extra_args_https }};
{% if ipv6 -%}
listen [::]:443 ssl{% if http2_support == 1 or http2_support == true %} http2{% endif %};
{% else -%}
#listen [::]:443;
{% endif %}
{% endif %}
listen [::]:{{ port_number_https }} {{ listen_extra_args_https }};
{% endif -%}
{% endif -%}

server_name {{ domain_names | join: " " }};
6 changes: 6 additions & 0 deletions backend/templates/_proxy_protocol.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{% if enable_proxy_protocol == 1 or enable_proxy_protocol == true %}
{% if load_balancer_ip != '' %}
set_real_ip_from {{ load_balancer_ip }};
real_ip_header proxy_protocol;
{% endif %}
{% endif %}
1 change: 1 addition & 0 deletions backend/templates/proxy_host.conf
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ server {
{% include "_exploits.conf" %}
{% include "_hsts.conf" %}
{% include "_forced_ssl.conf" %}
{% include "_proxy_protocol.conf" %}

{% if allow_websocket_upgrade == 1 or allow_websocket_upgrade == true %}
proxy_set_header Upgrade $http_upgrade;
Expand Down
19 changes: 13 additions & 6 deletions backend/templates/stream.conf
Original file line number Diff line number Diff line change
@@ -1,31 +1,38 @@
# ------------------------------------------------------------
# {{ incoming_port }} TCP: {{ tcp_forwarding }} UDP: {{ udp_forwarding }}
# ------------------------------------------------------------
{% if enable_proxy_protocol == 1 or enable_proxy_protocol == true -%}
{% capture listen_extra_args %}proxy_protocol{% endcapture -%}
{% endif -%}

{% if enabled %}
{% if tcp_forwarding == 1 or tcp_forwarding == true -%}
server {
listen {{ incoming_port }};
listen {{ incoming_port }} {{ listen_extra_args }};
{% if ipv6 -%}
listen [::]:{{ incoming_port }};
listen [::]:{{ incoming_port }} {{ listen_extra_args }};
{% else -%}
#listen [::]:{{ incoming_port }};
#listen [::]:{{ incoming_port }} {{ listen_extra_args }};
{% endif %}

proxy_pass {{ forwarding_host }}:{{ forwarding_port }};

{% include '_proxy_protocol.conf' %}

# Custom
include /data/nginx/custom/server_stream[.]conf;
include /data/nginx/custom/server_stream_tcp[.]conf;
}
{% endif %}
{% if udp_forwarding == 1 or udp_forwarding == true %}
{% # Proxy Protocol is not supported for UDP %}
{% assign listen_extra_args = "" %}
server {
listen {{ incoming_port }} udp;
listen {{ incoming_port }} udp {{ listen_extra_args }};
{% if ipv6 -%}
listen [::]:{{ incoming_port }} udp;
listen [::]:{{ incoming_port }} udp {{ listen_extra_args }};
{% else -%}
#listen [::]:{{ incoming_port }} udp;
#listen [::]:{{ incoming_port }} udp {{ listen_extra_args }};
{% endif %}
proxy_pass {{ forwarding_host }}:{{ forwarding_port }};

Expand Down
2 changes: 1 addition & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \
COPY docker/scripts/install-s6 /tmp/install-s6
RUN /tmp/install-s6 "${TARGETPLATFORM}" && rm -f /tmp/install-s6

EXPOSE 80 81 443
EXPOSE 80 81 88 443 444

COPY backend /app
COPY frontend/dist /app/frontend
Expand Down
2 changes: 1 addition & 1 deletion docker/dev/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,5 @@ RUN rm -f /etc/nginx/conf.d/production.conf \
COPY --from=pebbleca /test/certs/pebble.minica.pem /etc/ssl/certs/pebble.minica.pem
COPY --from=testca /home/step/certs/root_ca.crt /etc/ssl/certs/NginxProxyManager.crt

EXPOSE 80 81 443
EXPOSE 80 81 88 443 444
ENTRYPOINT [ "/init" ]
2 changes: 2 additions & 0 deletions docker/docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ services:
ports:
- 3080:80
- 3081:81
- 3088:88
- 3443:443
- 3444:444
networks:
nginx_proxy_manager:
aliases:
Expand Down
23 changes: 23 additions & 0 deletions docs/src/advanced-config/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,29 @@ You can customise the logrotate configuration through a mount (if your custom co

For reference, the default configuration can be found [here](https://github.com/NginxProxyManager/nginx-proxy-manager/blob/develop/docker/rootfs/etc/logrotate.d/nginx-proxy-manager).

## Enabling PROXY protocol for Proxy Hosts

When running NPM behind a load balancer, you might want to use the [PROXY procotol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) to receive client information such as the source IP address (useful for banning IPs).

When configuring the PROXY protocol for proxy hosts, NPM uses the ports 88 for http and 444 for https traffic to allow you to decide on a per host basis whether to use the PROSY protocol.

To enable the PROXY protocol for your hosts you need to perform the following steps:

1. Expose the ports `88` (and `444` is applicable) by adjusting your `docker-compose.yml`
2. Edit your proxy hosts to enable the PROXY protocol
3. Edit your upstream load balancer to redirect traffic to the port `88`/`444` and enable the PROXY protocol

## Enabling PROXY protocol for Streams

When running NPM behind a load balancer, you might want to use the [PROXY procotol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) to receive client information such as the source IP address (useful for banning IPs).

Keep in mind that the PROXY procotol cannot be enabled for udp endpoints.

To enable the PROXY protocol for streams:

1. Expose the desired port by adjusting you `docker-compose.yml`
2. Edit the Stream to enable the PROXY protocol
3. Edit your upstream load balancer to enable the PROXY protocol
## Enabling the geoip2 module

To enable the geoip2 module, you can create the custom configuration file `/data/nginx/custom/root_top.conf` and include the following snippet:
Expand Down
2 changes: 2 additions & 0 deletions docs/src/setup/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ services:
- '80:80' # Public HTTP Port
- '443:443' # Public HTTPS Port
- '81:81' # Admin Web Port
# - '88:88' # Public HTTP Port with proxy_protocol enabled
# - '444:444' # Public HTTPS Port with proxy_protocol enabled
# Add any other Stream port you want to expose
# - '21:21' # FTP
environment:
Expand Down
18 changes: 17 additions & 1 deletion frontend/js/app/nginx/proxy/form.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
</label>
</div>
</div>
<div class="col-sm-12 col-md-12">
<div class="col-sm-6 col-md-6">
<div class="form-group">
<label class="custom-switch">
<input type="checkbox" class="custom-switch-input" name="allow_websocket_upgrade" value="1"<%- allow_websocket_upgrade ? ' checked' : '' %>>
Expand All @@ -81,6 +81,22 @@
</label>
</div>
</div>
<div class="col-sm-6 col-md-6">
<div class="form-group">
<label class="custom-switch">
<input type="checkbox" class="custom-switch-input" name="enable_proxy_protocol" value="1"<%- enable_proxy_protocol ? ' checked' : '' %>>
<span class="custom-switch-indicator"></span>
<span class="custom-switch-description"><%- i18n('proxy-hosts', 'enable-proxy-protocol') %><a href="https://docs.nginx.com/nginx/admin-guide/load-balancer/using-proxy-protocol/#introduction" target="_blank"><i class="fe fe-help-circle"></i></a></span>
</label>
</div>
</div>

<div class="col-sm-12 col-md-12">
<div class="form-group">
<label class="form-label"><%- i18n('proxy-hosts', 'load-balancer-ip') %> <a href="https://docs.nginx.com/nginx/admin-guide/load-balancer/using-proxy-protocol/#changing-the-load-balancers-ip-address-to-the-client-ip-address" target="_blank"><i class="fe fe-help-circle"></i></a></label>
<input type="text" name="load_balancer_ip" class="form-control text-monospace" placeholder="" value="<%- load_balancer_ip %>" autocomplete="off" maxlength="255" <%- enable_proxy_protocol ? '' : ' disabled' %>>
</div>
</div>

<div class="col-sm-12 col-md-12">
<div class="form-group">
Expand Down
11 changes: 11 additions & 0 deletions frontend/js/app/nginx/proxy/form.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ module.exports = Mn.View.extend({
dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]',
propagation_seconds: 'input[name="meta[propagation_seconds]"]',
forward_scheme: 'select[name="forward_scheme"]',
enable_proxy_protocol: 'input[name="enable_proxy_protocol"]',
load_balancer_ip: 'input[name="load_balancer_ip"]',
letsencrypt: '.letsencrypt'
},

Expand All @@ -51,6 +53,13 @@ module.exports = Mn.View.extend({
},

events: {
'change @ui.enable_proxy_protocol': function () {
let checked = this.ui.enable_proxy_protocol.prop('checked');
this.ui.load_balancer_ip
.prop('disabled', !checked)
.parents('.form-group')
.css('opacity', checked ? 1 : 0.5);
},
'change @ui.certificate_select': function () {
let id = this.ui.certificate_select.val();
if (id === 'new') {
Expand Down Expand Up @@ -163,6 +172,7 @@ module.exports = Mn.View.extend({
data.block_exploits = !!data.block_exploits;
data.caching_enabled = !!data.caching_enabled;
data.allow_websocket_upgrade = !!data.allow_websocket_upgrade;
data.enable_proxy_protocol = !!data.enable_proxy_protocol;
data.http2_support = !!data.http2_support;
data.hsts_enabled = !!data.hsts_enabled;
data.hsts_subdomains = !!data.hsts_subdomains;
Expand Down Expand Up @@ -264,6 +274,7 @@ module.exports = Mn.View.extend({
onRender: function () {
let view = this;

this.ui.enable_proxy_protocol.trigger('change');
this.ui.ssl_forced.trigger('change');
this.ui.hsts_enabled.trigger('change');

Expand Down
16 changes: 16 additions & 0 deletions frontend/js/app/nginx/stream/form.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,22 @@
</label>
</div>
</div>
<div class="col-sm-6 col-md-6">
<div class="form-group">
<label class="custom-switch">
<input type="checkbox" class="custom-switch-input" name="enable_proxy_protocol" value="1"<%- enable_proxy_protocol ? ' checked' : '' %>>
<span class="custom-switch-indicator"></span>
<span class="custom-switch-description"><%- i18n('streams', 'enable-proxy-protocol') %><a href="https://docs.nginx.com/nginx/admin-guide/load-balancer/using-proxy-protocol/#introduction" target="_blank"><i class="fe fe-help-circle"></i></a></span>
</label>
</div>
</div>

<div class="col-sm-12 col-md-12">
<div class="form-group">
<label class="form-label"><%- i18n('streams', 'load-balancer-ip') %><a href="https://docs.nginx.com/nginx/admin-guide/load-balancer/using-proxy-protocol/#changing-the-load-balancers-ip-address-to-the-client-ip-address" target="_blank"><i class="fe fe-help-circle"></i></a></label>
<input type="text" name="load_balancer_ip" class="form-control text-monospace" placeholder="" value="<%- load_balancer_ip %>" autocomplete="off" maxlength="255" <%- enable_proxy_protocol ? '' : ' disabled' %>>
</div>
</div>
<div class="col-sm-12 col-md-12">
<div class="forward-type-error invalid-feedback"><%- i18n('streams', 'forward-type-error') %></div>
</div>
Expand Down
Loading