From a4f26a356afa74f375571abc57e5d87ebb4859ce Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Mon, 27 Apr 2026 02:55:30 -0400 Subject: [PATCH 1/5] feat: add nginx:reload command Reloads the nginx server config after validating it with `nginx -t`. Useful for picking up changes to globally managed nginx configs without restarting the running process. --- plugins/nginx-vhosts/command-functions | 23 +++++++++++++++++++++++ plugins/nginx-vhosts/help-functions | 1 + plugins/nginx-vhosts/subcommands/reload | 6 ++++++ 3 files changed, 30 insertions(+) create mode 100755 plugins/nginx-vhosts/subcommands/reload diff --git a/plugins/nginx-vhosts/command-functions b/plugins/nginx-vhosts/command-functions index a2de9beaf..5efbd339c 100755 --- a/plugins/nginx-vhosts/command-functions +++ b/plugins/nginx-vhosts/command-functions @@ -235,6 +235,29 @@ cmd-nginx-stop() { fn-nginx-vhosts-nginx-init-cmd "disable" } +cmd-nginx-reload() { + declare desc="reloads the nginx server config" + declare cmd="nginx:reload" + [[ "$1" == "$cmd" ]] && shift 1 + + local NGINX_LOCATION EXIT_CODE + NGINX_LOCATION=$(get_nginx_location) + if [[ -z "$NGINX_LOCATION" ]]; then + exit 1 + fi + + set +e + sudo "$NGINX_LOCATION" -t &>/dev/null + EXIT_CODE=$? + set -e + if [[ "$EXIT_CODE" -ne "0" ]]; then + sudo "$NGINX_LOCATION" -t + exit "$EXIT_CODE" + fi + + fn-nginx-vhosts-nginx-init-cmd "reload" +} + cmd-nginx-validate-config() { declare desc="validates and optionally cleans up invalid nginx configurations" declare cmd="nginx:validate-config" diff --git a/plugins/nginx-vhosts/help-functions b/plugins/nginx-vhosts/help-functions index 53219a912..9286119be 100755 --- a/plugins/nginx-vhosts/help-functions +++ b/plugins/nginx-vhosts/help-functions @@ -32,6 +32,7 @@ fn-help-content() { nginx:report [] [], Displays an nginx report for one or more apps nginx:set (), Set or clear an nginx property for an app nginx:show-config , Display app nginx config + nginx:reload, Reloads the nginx server config nginx:start, Starts the nginx server nginx:stop, Stops the nginx server nginx:validate-config [] [--clean], Validates and optionally cleans up invalid nginx configurations diff --git a/plugins/nginx-vhosts/subcommands/reload b/plugins/nginx-vhosts/subcommands/reload new file mode 100755 index 000000000..62edc03a2 --- /dev/null +++ b/plugins/nginx-vhosts/subcommands/reload @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +source "$PLUGIN_AVAILABLE_PATH/nginx-vhosts/command-functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +cmd-nginx-reload "$@" From d7c88ae298dad7b915ee5b38949996889e0220e0 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Mon, 27 Apr 2026 02:55:40 -0400 Subject: [PATCH 2/5] feat: ship default catch-all site on fresh apt install Fresh apt installs now drop a catch-all server block at `/etc/nginx/conf.d/00-default-vhost.conf` that uses `ssl_reject_handshake on` and `return 444` to drop requests with unknown Host headers. Conflicting upstream nginx default vhosts are renamed to `*.dokku-disabled` rather than deleted, preserving any local edits. The new `dokku/install_default_site` debconf flag opts out of the install. Upgrades leave existing nginx config untouched. --- debian/config | 1 + debian/postinst | 35 ++++++ debian/templates | 5 + docs/appendices/0.38.0-migration-guide.md | 6 + docs/networking/proxies/nginx.md | 117 +++++++++++++----- .../nginx-vhosts/templates/default-site.conf | 15 +++ tests/unit/nginx-vhosts_14.bats | 99 +++++++++++++++ 7 files changed, 246 insertions(+), 32 deletions(-) create mode 100644 plugins/nginx-vhosts/templates/default-site.conf create mode 100644 tests/unit/nginx-vhosts_14.bats diff --git a/debian/config b/debian/config index 99bba1b23..2a7b4576a 100755 --- a/debian/config +++ b/debian/config @@ -14,5 +14,6 @@ db_input "high" "dokku/hostname" || true db_input "high" "dokku/vhost_enable" || true if [ "$ACTION" != "reconfigure" ]; then db_input "high" "dokku/key_file" || true + db_input "high" "dokku/install_default_site" || true fi db_go || true diff --git a/debian/postinst b/debian/postinst index 8bef386ac..7e67da421 100755 --- a/debian/postinst +++ b/debian/postinst @@ -135,6 +135,38 @@ setup-sshcommand() { fi } +setup-default-site() { + db_get "dokku/install_default_site" + if [ "$RET" != "true" ]; then + return + fi + + local default_vhost_target="/etc/nginx/conf.d/00-default-vhost.conf" + local default_vhost_source="${DOKKU_LIB_ROOT}/core-plugins/available/nginx-vhosts/templates/default-site.conf" + + if [ -e "$default_vhost_target" ]; then + return + fi + + if [ ! -f "$default_vhost_source" ]; then + return + fi + + for path in /etc/nginx/sites-enabled/default /etc/nginx/sites-available/default /etc/nginx/conf.d/default.conf; do + if [ -e "$path" ] && [ ! -e "${path}.dokku-disabled" ]; then + mv -n "$path" "${path}.dokku-disabled" + echo "Disabled $path (renamed to ${path}.dokku-disabled)" 1>&2 + fi + done + + echo "Installing nginx default catch-all site at $default_vhost_target" + install -m 0644 -o root -g root "$default_vhost_source" "$default_vhost_target" + + if command -v dokku &>/dev/null; then + dokku nginx:reload || true + fi +} + dpkg-handling() { if [ -f "${DOKKU_ROOT}/VHOST" ]; then echo "VHOST file detected, skipping modification" @@ -170,6 +202,9 @@ case "$1" in setup-plugins setup-sshcommand setup-docker-live-restore + if [ -z "$2" ]; then + setup-default-site + fi ;; *) diff --git a/debian/templates b/debian/templates index 93e7a9768..51403a24e 100644 --- a/debian/templates +++ b/debian/templates @@ -22,3 +22,8 @@ Template: dokku/nginx_enable Description: Enable nginx-vhosts plugin? Type: boolean Default: true + +Template: dokku/install_default_site +Description: Install a catch-all nginx default site on fresh install? +Type: boolean +Default: true diff --git a/docs/appendices/0.38.0-migration-guide.md b/docs/appendices/0.38.0-migration-guide.md index e5e465ee3..5ff39fd7e 100644 --- a/docs/appendices/0.38.0-migration-guide.md +++ b/docs/appendices/0.38.0-migration-guide.md @@ -16,3 +16,9 @@ ``` - The path on disk to both the global `ENV` file and app `ENV` files have been moved. Users should reference environment variables via the provided plugin triggers rather than directly sourcing the ENV files. Existing ENV files are left untouched and will be removed on the subsequent Dokku install. +- During a fresh apt install, the upstream nginx default vhost files (`/etc/nginx/sites-enabled/default`, `/etc/nginx/sites-available/default`, and `/etc/nginx/conf.d/default.conf`) are renamed to `${path}.dokku-disabled` (not deleted) to avoid a `duplicate default server for 0.0.0.0:80` error. Operators with local customizations can recover them by inspecting the `.dokku-disabled` siblings. Upgrade-in-place installs do not touch any existing nginx files. +- Fresh apt installs now ship a catch-all default site at `/etc/nginx/conf.d/00-default-vhost.conf` that rejects requests with unknown Host headers using `ssl_reject_handshake on` (HTTPS) and `return 444` (HTTP). This replaces the manual workaround previously documented in the nginx docs. The behavior can be opted out at install time via the `dokku/install_default_site` debconf prompt. See the [Default site documentation](/docs/networking/proxies/nginx.md#default-site). + +### TLS handshake behavior change + +With the new catch-all installed, an HTTPS request to a hostname that matches a configured dokku app but where the app has no TLS certificate configured will have its TLS handshake rejected by the catch-all (via `ssl_reject_handshake on`). Previously, nginx fell through to the lexicographically first port-443 server block and presented that block's certificate, producing a cert-mismatch error on the client. The new behavior is a correctness improvement, but operators who deliberately relied on the old fall-through certificate (for monitoring probes, for example) need to either configure a certificate for the target app or remove the catch-all on that host. Existing apps that already have certificates configured are unaffected: nginx selects the right server block via SNI before TLS completion, so the catch-all is never consulted for legitimate requests. diff --git a/docs/networking/proxies/nginx.md b/docs/networking/proxies/nginx.md index 9dec59cfb..2e746c0e7 100644 --- a/docs/networking/proxies/nginx.md +++ b/docs/networking/proxies/nginx.md @@ -5,6 +5,7 @@ Dokku uses nginx as its server for routing requests to specific applications. By ``` nginx:access-logs [-t] # Show the nginx access logs for an application (-t follows) nginx:error-logs [-t] # Show the nginx error logs for an application (-t follows) +nginx:reload # Reloads the nginx server config nginx:report [] [|--format json] # Displays a nginx report for one or more apps nginx:set () # Set or clear an nginx property for an app nginx:show-config # Display app nginx config @@ -64,6 +65,19 @@ The nginx server can be stopped via `nginx:stop`. dokku nginx:stop ``` +### Reloading nginx + +> [!IMPORTANT] +> New as of 0.38.0 + +The nginx server can be reloaded via `nginx:reload`. This validates the current nginx configuration and, if valid, signals nginx to reload without restarting the running process. This is the preferred command after editing files in `/etc/nginx/conf.d/` directly. + +```shell +dokku nginx:reload +``` + +If the configuration is invalid, the command exits non-zero and prints the validation error without reloading. + ### Checking access logs > [!NOTE] @@ -168,52 +182,91 @@ These are provided as an alternative to the generic Nginx error page, are shared ### Default site -By default, Dokku will route any received request with an unknown HOST header value to the lexicographically first site in the nginx config stack. This means that accessing the dokku server via its IP address or a bogus domain name may return a seemingly random website. +> [!IMPORTANT] +> New as of 0.38.0 -> [!WARNING] -> Some versions of Nginx may create a default site when installed. This site is simply a static page which says "Welcome to Nginx", and if this default site is enabled, Nginx will not route any requests with an unknown HOST header to Dokku. If you want Dokku to receive all requests, run the following commands: -> -> ``` -> rm /etc/nginx/sites-enabled/default -> dokku nginx:stop -> dokku nginx:start -> ``` +On fresh apt installs, Dokku ships a catch-all default site at `/etc/nginx/conf.d/00-default-vhost.conf` that rejects requests whose Host header (or TLS SNI) does not match any deployed app. The catch-all uses [`ssl_reject_handshake on`](https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_reject_handshake) for HTTPS and [`return 444`](https://nginx.org/en/docs/http/ngx_http_rewrite_module.html#return) for HTTP. Both close the connection without sending a response. -If services should only be accessed via their domain name, you may want to disable the default site by adding the following configuration to the global nginx configuration. - -Create the file at `/etc/nginx/conf.d/00-default-vhost.conf`: +The shipped file looks like: ```nginx server { - listen 80 default_server; - listen [::]:80 default_server; - - # If services hosted by dokku are available via HTTPS, it is recommended - # to also uncomment the following section. - # - # Please note that in order to let this work, you need an SSL certificate. However - # it does not need to be valid. Users of Debian-based distributions can install the - # `ssl-cert` package with `sudo apt install ssl-cert` to automatically generate - # a self-signed certificate that is stored at `/etc/ssl/certs/ssl-cert-snakeoil.pem`. - # - #listen 443 ssl; - #listen [::]:443 ssl; - #ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem; - #ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key; + listen 80 default_server; + listen [::]:80 default_server; + listen 443 ssl default_server; + listen [::]:443 ssl default_server; server_name _; - access_log off; + access_log off; + + ssl_reject_handshake on; return 444; } ``` -Make sure to reload nginx after creating this file by running `systemctl reload nginx.service`. +The `00-` prefix forces nginx to load this file before `/etc/nginx/conf.d/dokku.conf`, so its `default_server` markers establish the default for each port before any per-app server blocks are loaded. -This will catch all unknown HOST header values and close the connection without responding. You can replace the `return 444;` with `return 410;` which will cause nginx to respond with an error page. +#### TLS handshake behavior -The configuration file must be loaded before `/etc/nginx/conf.d/dokku.conf`, so it can not be arranged as a vhost in `/etc/nginx/sites-enabled` that is only processed afterwards. +The catch-all does not affect TLS handshakes for legitimate apps. nginx selects the matching server block via SNI before completing the handshake; only requests that have no matching app fall through to the catch-all. -Alternatively, you may push an app to your Dokku host with a name like "00-default". As long as it lists first in `ls /home/dokku/*/nginx.conf | head`, it will be used as the default nginx vhost. +| Request | Result | +| --- | --- | +| HTTPS to a configured app's hostname (with matching SNI) and the app has a cert | Handshake completes with the app's cert. Catch-all not consulted. | +| HTTPS to a configured app's hostname when the app has no cert configured | Handshake rejected by the catch-all. (Previously: nginx fell through to the lexicographically first port-443 server block and presented its cert, producing a confusing cert-mismatch error.) | +| HTTPS to the server's IP with no SNI, or with an SNI matching no app | Handshake rejected. | +| HTTP to a configured app's hostname | Routed normally to that app. Catch-all not consulted. | +| HTTP to a hostname matching no app | Catch-all `return 444`. | + +#### Conflicting upstream nginx default vhost + +A fresh nginx install ships its own default vhost that also claims port 80 with `default_server`, which would cause `nginx -t` to fail with `a duplicate default server for 0.0.0.0:80` once the dokku catch-all is in place. To avoid this, the dokku postinst renames any of the following files in place to `${path}.dokku-disabled` instead of deleting them, preserving any local customizations: + +- `/etc/nginx/sites-enabled/default` +- `/etc/nginx/sites-available/default` +- `/etc/nginx/conf.d/default.conf` + +To recover an original file, inspect the `.dokku-disabled` sibling. + +#### Disabling the catch-all on first install + +Select "No" at the `dokku/install_default_site` debconf prompt, or pre-seed the answer before installing: + +```shell +echo 'dokku dokku/install_default_site boolean false' | debconf-set-selections +``` + +#### Customizing or removing the catch-all after install + +To customize the reject behavior, edit `/etc/nginx/conf.d/00-default-vhost.conf` and reload: + +```shell +dokku nginx:reload +``` + +For example, replace `return 444;` with `return 410;` to respond with an HTTP 410 Gone error page instead of dropping the connection. + +To disable the catch-all without uninstalling dokku: + +```shell +mv /etc/nginx/conf.d/00-default-vhost.conf{,.disabled} +dokku nginx:reload +``` + +#### Installing the catch-all on upgraded or non-apt installs + +Existing dokku installs upgraded from earlier versions do not get the catch-all installed automatically; the upgrade leaves their nginx configuration untouched. To install it manually, copy the shipped template into place and reload nginx: + +```shell +cp /var/lib/dokku/core-plugins/available/nginx-vhosts/templates/default-site.conf /etc/nginx/conf.d/00-default-vhost.conf +dokku nginx:reload +``` + +If the upstream nginx default vhost is also active, disable it first (e.g. `mv /etc/nginx/sites-enabled/default{,.disabled}`) to avoid a duplicate-default-server error. + +#### App-name based alternative + +As an alternative to the catch-all file, you may push an app to your Dokku host with a name like `00-default`. As long as it lists first in `ls /home/dokku/*/nginx.conf | head`, it will be used as the default nginx vhost. ### Customizing the nginx configuration diff --git a/plugins/nginx-vhosts/templates/default-site.conf b/plugins/nginx-vhosts/templates/default-site.conf new file mode 100644 index 000000000..92f4fdb8e --- /dev/null +++ b/plugins/nginx-vhosts/templates/default-site.conf @@ -0,0 +1,15 @@ +# Catch-all server block for requests with unknown Host headers. +# Installed on fresh apt installs of dokku; safe to edit. +# See /etc/nginx/conf.d/dokku.conf for the per-app server blocks. +server { + listen 80 default_server; + listen [::]:80 default_server; + listen 443 ssl default_server; + listen [::]:443 ssl default_server; + + server_name _; + access_log off; + + ssl_reject_handshake on; + return 444; +} diff --git a/tests/unit/nginx-vhosts_14.bats b/tests/unit/nginx-vhosts_14.bats new file mode 100644 index 000000000..46d424064 --- /dev/null +++ b/tests/unit/nginx-vhosts_14.bats @@ -0,0 +1,99 @@ +#!/usr/bin/env bats + +# Tests for the nginx:reload command and the catch-all default site shipped +# at /etc/nginx/conf.d/00-default-vhost.conf. Postinst-driven behavior +# (debconf flag, fresh-install detection, sites-enabled/default rename) is +# not exercised here because the bats harness does not reinstall the apt +# package; the rename logic is exercised manually by simulating the +# conflicting upstream vhost. + +load test_helper + +NGINX_DEFAULT_VHOST_PATH="/etc/nginx/conf.d/00-default-vhost.conf" +NGINX_DEFAULT_VHOST_SOURCE="/var/lib/dokku/core-plugins/available/nginx-vhosts/templates/default-site.conf" + +setup() { + global_setup + [[ -f "$DOKKU_ROOT/VHOST" ]] && cp -fp "$DOKKU_ROOT/VHOST" "$DOKKU_ROOT/VHOST.bak" + [[ -f "$NGINX_DEFAULT_VHOST_PATH" ]] && cp -fp "$NGINX_DEFAULT_VHOST_PATH" "${NGINX_DEFAULT_VHOST_PATH}.bats-bak" + rm -f "$NGINX_DEFAULT_VHOST_PATH" +} + +teardown() { + rm -f "$NGINX_DEFAULT_VHOST_PATH" + if [[ -f "${NGINX_DEFAULT_VHOST_PATH}.bats-bak" ]]; then + mv "${NGINX_DEFAULT_VHOST_PATH}.bats-bak" "$NGINX_DEFAULT_VHOST_PATH" + fi + rm -f /etc/nginx/sites-enabled/default.bats-stub + rm -f /etc/nginx/sites-enabled/default.bats-stub.dokku-disabled + [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" + if sudo nginx -t &>/dev/null; then + sudo systemctl reload nginx || true + fi + global_teardown +} + +@test "(nginx-vhosts:reload) reloads with valid config" { + run /bin/bash -c "dokku nginx:reload" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(nginx-vhosts:reload) fails with invalid config without restarting" { + local invalid_conf="/etc/nginx/conf.d/dokku-bats-invalid.conf" + echo "this is not valid nginx config" | sudo tee "$invalid_conf" >/dev/null + + run /bin/bash -c "dokku nginx:reload" + echo "output: $output" + echo "status: $status" + assert_failure + + sudo rm -f "$invalid_conf" +} + +@test "(nginx-vhosts) [default-site] shipped template installs and validates" { + [[ -f "$NGINX_DEFAULT_VHOST_SOURCE" ]] || skip "default-site template not installed; run 'sudo make copyfiles'" + + sudo install -m 0644 -o root -g root "$NGINX_DEFAULT_VHOST_SOURCE" "$NGINX_DEFAULT_VHOST_PATH" + + run /bin/bash -c "sudo grep -F 'ssl_reject_handshake on' '$NGINX_DEFAULT_VHOST_PATH'" + assert_success + run /bin/bash -c "sudo grep -F 'return 444' '$NGINX_DEFAULT_VHOST_PATH'" + assert_success + run /bin/bash -c "sudo grep -F 'default_server' '$NGINX_DEFAULT_VHOST_PATH'" + assert_success + + run /bin/bash -c "sudo nginx -t" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(nginx-vhosts) [default-site] coexists with stock sites-enabled/default" { + [[ -f "$NGINX_DEFAULT_VHOST_SOURCE" ]] || skip "default-site template not installed; run 'sudo make copyfiles'" + + sudo mkdir -p /etc/nginx/sites-enabled + sudo tee /etc/nginx/sites-enabled/default.bats-stub >/dev/null <<'STOCK' +server { + listen 80 default_server; + listen [::]:80 default_server; + server_name _; + return 200 "stock default"; +} +STOCK + + sudo install -m 0644 -o root -g root "$NGINX_DEFAULT_VHOST_SOURCE" "$NGINX_DEFAULT_VHOST_PATH" + + run /bin/bash -c "sudo nginx -t" + echo "output: $output" + echo "status: $status" + assert_failure + + sudo mv /etc/nginx/sites-enabled/default.bats-stub /etc/nginx/sites-enabled/default.bats-stub.dokku-disabled + + run /bin/bash -c "sudo nginx -t" + echo "output: $output" + echo "status: $status" + assert_success +} From b2ea5406fee3b7b4362ab1ad3e185e1ebe80461b Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Mon, 27 Apr 2026 04:02:18 -0400 Subject: [PATCH 3/5] fix: remove sites-enabled default instead of renaming Renaming `/etc/nginx/sites-enabled/default` to `.dokku-disabled` left a broken symlink that nginx's `include /etc/nginx/sites-enabled/*;` glob still picked up, breaking config validation. Files inside `sites-enabled/` are now removed; the real config in `sites-available/default` is still preserved via rename. --- debian/postinst | 35 +++++++++++++++++++++++++++++---- tests/unit/nginx-vhosts_14.bats | 3 +-- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/debian/postinst b/debian/postinst index 7e67da421..06860f8d0 100755 --- a/debian/postinst +++ b/debian/postinst @@ -135,6 +135,36 @@ setup-sshcommand() { fi } +disable-default-vhost-path() { + local path="$1" + local backup_path="${path}.dokku-disabled" + + if [ ! -e "$path" ] && [ ! -L "$path" ]; then + return + fi + + if [ -e "$backup_path" ] || [ -L "$backup_path" ]; then + return + fi + + case "$path" in + /etc/nginx/sites-enabled/*) + rm -f "$path" + echo "Removed conflicting nginx default at $path" 1>&2 + return + ;; + esac + + if [ -L "$path" ]; then + rm -f "$path" + echo "Removed conflicting nginx default symlink at $path" 1>&2 + return + fi + + mv -n "$path" "$backup_path" + echo "Disabled $path (renamed to $backup_path)" 1>&2 +} + setup-default-site() { db_get "dokku/install_default_site" if [ "$RET" != "true" ]; then @@ -153,10 +183,7 @@ setup-default-site() { fi for path in /etc/nginx/sites-enabled/default /etc/nginx/sites-available/default /etc/nginx/conf.d/default.conf; do - if [ -e "$path" ] && [ ! -e "${path}.dokku-disabled" ]; then - mv -n "$path" "${path}.dokku-disabled" - echo "Disabled $path (renamed to ${path}.dokku-disabled)" 1>&2 - fi + disable-default-vhost-path "$path" done echo "Installing nginx default catch-all site at $default_vhost_target" diff --git a/tests/unit/nginx-vhosts_14.bats b/tests/unit/nginx-vhosts_14.bats index 46d424064..51046b609 100644 --- a/tests/unit/nginx-vhosts_14.bats +++ b/tests/unit/nginx-vhosts_14.bats @@ -25,7 +25,6 @@ teardown() { mv "${NGINX_DEFAULT_VHOST_PATH}.bats-bak" "$NGINX_DEFAULT_VHOST_PATH" fi rm -f /etc/nginx/sites-enabled/default.bats-stub - rm -f /etc/nginx/sites-enabled/default.bats-stub.dokku-disabled [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" if sudo nginx -t &>/dev/null; then sudo systemctl reload nginx || true @@ -90,7 +89,7 @@ STOCK echo "status: $status" assert_failure - sudo mv /etc/nginx/sites-enabled/default.bats-stub /etc/nginx/sites-enabled/default.bats-stub.dokku-disabled + sudo rm -f /etc/nginx/sites-enabled/default.bats-stub run /bin/bash -c "sudo nginx -t" echo "output: $output" From af6267f3b51ee167bc9bf1f5308adb49569dad29 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Mon, 27 Apr 2026 07:30:54 -0400 Subject: [PATCH 4/5] fix: restore default domains and reset proxy ports on vhost enable Re-enabling vhost (via `proxy:enable`, `domains:enable`, or `domains:add` with no global VHOST) was leaving the app pinned to the random high port and the empty VHOST file from the previous disabled state, so the new catch-all vhost intercepted requests for the app's domain. The `domains-enable` trigger now restores default vhosts via `domains_enable`, and `pre-enable-vhost` clears any stored proxy ports so they can be recomputed to 80/443. Also fixes the netrc lookup in the `git:auth` test to read dokku's netrc file directly. --- plugins/domains/domains-enable | 4 ++-- plugins/nginx-vhosts/pre-enable-vhost | 6 +++--- tests/unit/git_3.bats | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/domains/domains-enable b/plugins/domains/domains-enable index 5e3127ba9..1070b8c06 100755 --- a/plugins/domains/domains-enable +++ b/plugins/domains/domains-enable @@ -9,9 +9,9 @@ trigger-domains-domains-enable() { declare APP="$1" RESTART_APP="$2" if [[ "$RESTART_APP" == "true" ]]; then - enable_app_vhost "$APP" + domains_enable "$APP" else - enable_app_vhost "$APP" --no-restart + domains_enable "$APP" --no-restart fi } diff --git a/plugins/nginx-vhosts/pre-enable-vhost b/plugins/nginx-vhosts/pre-enable-vhost index 224887462..a64365d66 100755 --- a/plugins/nginx-vhosts/pre-enable-vhost +++ b/plugins/nginx-vhosts/pre-enable-vhost @@ -13,14 +13,14 @@ trigger-nginx-vhosts-pre-enable-vhost() { return fi - # only clear ports if they are port 80 or 443 + # clear stored proxy ports so they can be recomputed for the enabled state local PROXY_PORT=$(plugn trigger ports-get-property "$APP" "proxy-port") local PROXY_SSL_PORT=$(plugn trigger ports-get-property "$APP" "proxy-ssl-port") - if [[ "$PROXY_PORT" == "80" ]]; then + if [[ -n "$PROXY_PORT" ]]; then fn-plugin-property-delete "proxy" "$APP" "proxy-port" fi - if [[ "$PROXY_SSL_PORT" == "443" ]]; then + if [[ -n "$PROXY_SSL_PORT" ]]; then fn-plugin-property-delete "proxy" "$APP" "proxy-ssl-port" fi diff --git a/tests/unit/git_3.bats b/tests/unit/git_3.bats index aa4cce84c..21a7e06bb 100644 --- a/tests/unit/git_3.bats +++ b/tests/unit/git_3.bats @@ -100,7 +100,7 @@ teardown() { assert_success assert_output_contains "Setting netrc auth entry for host github.com" - run /bin/bash -c "netrc get github.com" + run /bin/bash -c "netrc get --netrc-file ${DOKKU_ROOT}/.netrc github.com" echo "output: $output" echo "status: $status" assert_success From eaaf0e41c2413c871af6d0ff338cf340f747a8a1 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Mon, 27 Apr 2026 15:43:33 -0400 Subject: [PATCH 5/5] fix: clear auto-detected port maps when re-enabling vhost Re-enabling vhost on an app that was previously deployed with no global VHOST left the auto-assigned random high port behind in the ports plugin's `map-detected` property, so nginx kept listening on that port and the new catch-all default vhost rejected requests for the app's own domain on port 80. --- plugins/nginx-vhosts/pre-enable-vhost | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/nginx-vhosts/pre-enable-vhost b/plugins/nginx-vhosts/pre-enable-vhost index a64365d66..c3abb8439 100755 --- a/plugins/nginx-vhosts/pre-enable-vhost +++ b/plugins/nginx-vhosts/pre-enable-vhost @@ -24,6 +24,9 @@ trigger-nginx-vhosts-pre-enable-vhost() { fn-plugin-property-delete "proxy" "$APP" "proxy-ssl-port" fi + # clear auto-detected port maps so they get recomputed for vhost mode + fn-plugin-property-delete "ports" "$APP" "map-detected" + # clear ports if there is a mapping starting with http:80 or https:443 local PORT_MAPS=$(plugn trigger ports-get "$APP") for PORT_MAP in $PORT_MAPS; do