Converting nginx config to caddy

1. Output of caddy version:

2. How I run Caddy:

v2.5.2 h1:eCJdLyEyAGzuQTa5Mh3gETnYWDClo1LjtQm2q9RNZrs=

a. System environment:

Ubuntu 20.04.4 LTS, sh script

b. Command:

/usr/local/bin/caddy run --environ --config /etc/caddy/Caddyfile --adapter caddyfile

c. Service/unit/compose file:

#!/bin/bash
/usr/local/bin/caddy run --environ --config /etc/caddy/Caddyfile --adapter caddyfile

d. My complete Caddy config:

{
	order request_id before header
	http_port 8080
	https_port 8443
	auto_https off
	log stdout
	grace_period 10s
}

http://portal.user.local {
	log
	request_id

	header * X-Request-Id "{http.request_id}"

	# list of whitelisted ips.
	# Does not seems to carry over to handle*
	@blocked not remote_ip forwarded 127.0.0.1
	respond @blocked "Access Denied." 403

	root * /home/user/public

	handle_path /assets/* {
		root * /home/user/assets
		file_server
	}

	handle_path /static/* {
		root * /home/user/static
		file_server
	}

	php_fastcgi backend_php:9000 {
		trusted_proxies private_ranges

		env SCRIPT_FILENAME /app/public/index.php
		env X_REQUEST_ID "{http.request_id}"

		@is_files header X-Accel-Redirect /_files/*
		@is_assets header X-Accel-Redirect /_assets/*

		handle_response @is_files {
			rewrite {http.reverse_proxy.header.X-Accel-Redirect}
			root * /home/user/private/storage
			uri strip_prefix "/_files"
			file_server
		}

		handle_response @is_assets {
			rewrite {http.reverse_proxy.header.X-Accel-Redirect}
			root * /home/user/private/assets
			uri strip_prefix "/_assets"
			file_server
		}
	}

	file_server
}

3. The problem I’m having:

I have this full nginx config and im trying to convert to caddy. However, i hit a dead end i cannot grasp how to handle specific section of the config the full config as the following

server {
    root /home/user/public;

    server_name portal.user.local;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico {
        access_log off;
        log_not_found off;
        return 204;
    }

    location /_files/ {
        internal;
        expires 30d;
        alias /home/user/private/storage/;
    }

    location /_assets/ {
        internal;
        expires 30d;
        alias /home/user/private/assets/;
    }

    location /assets/ {
         alias /home/user/assets/;
         try_files $uri =404;
    }

    location /static/ {
         alias /home/user/static/;
         try_files $uri =404;
    }

    location ~ ^/_hosted_files/(.*?)/(.*?)/(.*) {
        # Only allow internal redirects
        internal;

        # Extract the remote URL parts
        set $download_scheme $1;
        set $download_host $2;
        set $download_path $3;

        #  Construct the remote URL
        set $download_url $download_scheme://$download_host/$download_path;

        # Headers for the remote server, unset Authorization and Cookie for security reasons.
        resolver 127.0.0.53 ipv6=off;
        proxy_ssl_server_name                   on;
        proxy_pass_request_headers              on;
        proxy_set_header Host                   $download_host;
        proxy_set_header Authorization          '';
        proxy_set_header Cookie                 '';
        proxy_set_header X-Apikey               'app_api_key';

        # Headers for the response, by using $upstream_http_... here we can inject
        # other headers from Django, proxy_hide_header ensures the header from the
        # remote server isn't passed through.
        proxy_hide_header Content-Disposition;
        add_header Content-Disposition $upstream_http_content_disposition;

        # Stops the local disk from being written to (just forwards data through)
        proxy_max_temp_file_size 0;

        # For performance
        proxy_buffering off;

        # Proxy the remote file through to the client
        proxy_pass $download_url$is_args$args;
    }

    location ~ ^/esevent/(.*?)/ {
        # Protect url
        satisfy any;

        # removed ips.
        allow 127.0.0.1;

        deny all;
        #End

        set $event_name $1;
        set $event_host 'es_main.portal.user.local';

        if ($event_name = "backup") {
            set $event_host 'es_backup.portal.user.local';
        }

        resolver 127.0.0.53 ipv6=off;
        proxy_ssl_server_name                   on;
        proxy_pass_request_headers              on;
        proxy_buffering                         off;
        proxy_cache                             off;
        proxy_set_header Host                   $event_host;
        proxy_set_header Connection             '';
        proxy_http_version                      1.1;
        chunked_transfer_encoding               off;
        proxy_set_header X-Apikey               'app_api_key';

        proxy_pass https://$event_host/logs/stream/$is_args$args;
    }

    location ~* (index|test)\.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass backend_php:9000;
    }

}

i managed to convert the majority of the config. however this part

server {

    location ~ ^/_hosted_files/(.*?)/(.*?)/(.*) {
        # Only allow internal redirects
        internal;

        # Extract the remote URL parts
        set $download_scheme $1;
        set $download_host $2;
        set $download_path $3;

        #  Construct the remote URL
        set $download_url $download_scheme://$download_host/$download_path;

        # Headers for the remote server, unset Authorization and Cookie for security reasons.
        resolver 127.0.0.53 ipv6=off;
        proxy_ssl_server_name                   on;
        proxy_pass_request_headers              on;
        proxy_set_header Host                   $download_host;
        proxy_set_header Authorization          '';
        proxy_set_header Cookie                 '';
        proxy_set_header X-Apikey               'app_api_key';

        # Headers for the response, by using $upstream_http_... here we can inject
        # other headers from Django, proxy_hide_header ensures the header from the
        # remote server isn't passed through.
        proxy_hide_header Content-Disposition;
        add_header Content-Disposition $upstream_http_content_disposition;

        # Stops the local disk from being written to (just forwards data through)
        proxy_max_temp_file_size 0;

        # For performance
        proxy_buffering off;

        # Proxy the remote file through to the client
        proxy_pass $download_url$is_args$args;
    }

    location ~ ^/esevent/(.*?)/ {
        # Protect url
        satisfy any;

        # removed ips.
        allow 127.0.0.1;

        deny all;
        #End

        set $event_name $1;
        set $event_host 'es_main.portal.user.local';

        if ($event_name = "backup") {
            set $event_host 'es_backup.portal.user.local';
        }

        resolver 127.0.0.53 ipv6=off;
        proxy_ssl_server_name                   on;
        proxy_pass_request_headers              on;
        proxy_buffering                         off;
        proxy_cache                             off;
        proxy_set_header Host                   $event_host;
        proxy_set_header Connection             '';
        proxy_http_version                      1.1;
        chunked_transfer_encoding               off;
        proxy_set_header X-Apikey               'app_api_key';

        proxy_pass https://$event_host/logs/stream/$is_args$args;
    }
}

is hard for me to translate to caddy syntax, im not sure if it even possible

4. Error messages and/or full log output:

No error messages.

5. What I already tried:

I am out of my depth trying to convert the config to caddy, i managed to convert what i could. However going deep and running circles in docs for the last 3 hours and i couldn’t see how i can convert this. either i am blind or the docs does not have examples to convert this kind of config. :frowning:

If anyone has an idea on how to redo this part in caddy i’ll be grateful, i already moved 28 sites over to caddy only two that has the same problem left.

6. Links to relevant resources:

Which part are you having trouble with? Which “specific section”?

1 Like

Hi thank you for your reply.

its mainly the regex part how would you approach writing it in caddy. the first section

location ~ ^/_hosted_files/(.*?)/(.*?)/(.*) {
  }

is parsed against upstream header X-Accel-Redirect response.

@user_files header X-Accel-Redirect /_hosted_files/*/*/*

handle_response @user_files {
    # how to accomplish this
    regex {http.reverse_proxy.header.X-Accel-Redirect} “^/_hosted_files/(.*?)/(.*?)/(.*)“
    reverse_proxy {re.match.1}://{re.match.2}/{re.match.3}
 }

 
1 Like

It’s not possible to use a placeholder for the proxy scheme. It needs to either be always HTTP or always HTTPS. See the reverse_proxy docs for how to deal with headers when proxying over HTTPS.

And it’s not possible to do a path rewrite in reverse_proxy, you need to do it ahead of time with the rewrite directive.

You can use the path_regexp or header_regexp matcher to extract parts of the path or header respectively:

1 Like

hi i dont mind setting static http or https for the scheme i tried header_regexp first but it seems its not supported in response context?

and sorry for the short replies using my phone as i am away from my pc

You can’t use those matchers on handle_response, but you can use it inside of handle_response.

You can do a rewrite * {http.reverse_proxy.header.X-Accel-Redirect} to set the path, then use path_regexp to manipulate it.

2 Likes

Thank you very much. i will have have better look at this tomorrow

Hi

so far this is what i come up with

{
	admin 127.0.0.1:20199
	debug
	log stdout
}

:7885 {
	root * ./data

	php_fastcgi unix//var/run/php/php8.1-fpm.sock {
		@is_files header X-Accel-Redirect /_files/*

		handle_response @is_files {
			route {
				header X-Internal-Link {http.reverse_proxy.header.X-Accel-Redirect}
				rewrite * {http.reverse_proxy.header.X-Accel-Redirect}

				@parts path_regexp parts `/_files/(.*?)/(.*?)/(.*?)/(.*)`

				# This rewrite is not being carried down to the reverse_proxy
				rewrite * /assets/{re.parts.3}/{re.parts.4}

				reverse_proxy {re.parts.2}:7886
			}
		}
	}
}

:7886 {
	root * ./assets
	file_server
}

this is log for verifying that :7886 actually has the file

2022/07/29 08:43:05.497 DEBUG   http.handlers.file_server       sanitized path join     {"site_root": "./assets", "request_path": "/baz/file.txt", "result": "assets/baz/file.txt"}
2022/07/29 08:43:05.497 DEBUG   http.handlers.file_server       opening file    {"filename": "assets/baz/file.txt"}

This is the log for :7885 which is failing to rewrite the path

2022/07/29 08:43:08.718 DEBUG   http.handlers.rewrite   rewrote request {"request": {"remote_ip": "127.0.0.1", "remote_port": "53926", "proto": "HTTP/1.1", "method": "GET", "host": "localhost:7885", "uri": "/", "headers": {"Accept": ["*/*"], "Connection": ["keep-alive"], "User-Agent": ["HTTPie/1.0.3"], "Accept-Encoding": ["gzip, deflate"]}}, "method": "GET", "uri": "/index.php"}
2022/07/29 08:43:08.718 DEBUG   http.handlers.reverse_proxy     selected upstream       {"dial": "/var/run/php/php8.1-fpm.sock", "total_upstreams": 1}
2022/07/29 08:43:08.719 DEBUG   http.reverse_proxy.transport.fastcgi    roundtrip       {"request": {"remote_ip": "127.0.0.1", "remote_port": "53926", "proto": "HTTP/1.1", "method": "GET", "host": "localhost:7885", "uri": "/index.php", "headers": {"Accept-Encoding": ["gzip, deflate"], "Accept": ["*/*"], "X-Forwarded-For": ["127.0.0.1"], "User-Agent": ["HTTPie/1.0.3"], "X-Forwarded-Proto": ["http"], "X-Forwarded-Host": ["localhost:7885"]}}, "dial": "/var/run/php/php8.1-fpm.sock", "env": {"PATH_INFO": "", "CONTENT_LENGTH": "", "CONTENT_TYPE": "", "AUTH_TYPE": "", "HTTP_X_FORWARDED_FOR": "127.0.0.1", "REMOTE_HOST": "127.0.0.1", "REMOTE_USER": "", "SERVER_NAME": "localhost", "SERVER_PORT": "7885", "REMOTE_IDENT": "", "REMOTE_ADDR": "127.0.0.1", "SCRIPT_NAME": "/index.php", "HTTP_ACCEPT": "*/*", "HTTP_X_FORWARDED_PROTO": "http", "REQUEST_SCHEME": "http", "DOCUMENT_URI": "/index.php", "SCRIPT_FILENAME": "/home/user/tests/test_caddy/data/index.php", "HTTP_X_FORWARDED_HOST": "localhost:7885", "SERVER_PROTOCOL": "HTTP/1.1", "HTTP_HOST": "localhost:7885", "REQUEST_URI": "/", "HTTP_ACCEPT_ENCODING": "gzip, deflate", "HTTP_USER_AGENT": "HTTPie/1.0.3", "REMOTE_PORT": "53926", "REQUEST_METHOD": "GET", "SERVER_SOFTWARE": "Caddy/v2.5.2", "DOCUMENT_ROOT": "/home/user/tests/test_caddy/data", "GATEWAY_INTERFACE": "CGI/1.1", "QUERY_STRING": ""}}
2022/07/29 08:43:08.719 DEBUG   http.handlers.reverse_proxy     upstream roundtrip      {"upstream": "unix//var/run/php/php8.1-fpm.sock", "duration": 0.000320363, "request": {"remote_ip": "127.0.0.1", "remote_port": "53926", "proto": "HTTP/1.1", "method": "GET", "host": "localhost:7885", "uri": "/index.php", "headers": {"X-Forwarded-For": ["127.0.0.1"], "User-Agent": ["HTTPie/1.0.3"], "X-Forwarded-Proto": ["http"], "X-Forwarded-Host": ["localhost:7885"], "Accept-Encoding": ["gzip, deflate"], "Accept": ["*/*"]}}, "headers": {"X-Accel-Redirect": ["/_files/https/localhost/baz/file.txt"], "Content-Type": ["text/html; charset=UTF-8"]}, "status": 200}
2022/07/29 08:43:08.719 DEBUG   http.handlers.reverse_proxy     handling response       {"handler": 0}
2022/07/29 08:43:08.719 DEBUG   http.handlers.rewrite   rewrote request {"request": {"remote_ip": "127.0.0.1", "remote_port": "53926", "proto": "HTTP/1.1", "method": "GET", "host": "localhost:7885", "uri": "/index.php", "headers": {"User-Agent": ["HTTPie/1.0.3"], "Accept-Encoding": ["gzip, deflate"], "Accept": ["*/*"], "Connection": ["keep-alive"]}}, "method": "GET", "uri": "/_files/https/localhost/baz/file.txt"}
2022/07/29 08:43:08.719 DEBUG   http.handlers.rewrite   rewrote request {"request": {"remote_ip": "127.0.0.1", "remote_port": "53926", "proto": "HTTP/1.1", "method": "GET", "host": "localhost:7885", "uri": "/_files/https/localhost/baz/file.txt", "headers": {"User-Agent": ["HTTPie/1.0.3"], "Accept-Encoding": ["gzip, deflate"], "Accept": ["*/*"], "Connection": ["keep-alive"]}}, "method": "GET", "uri": "/assets//"}
2022/07/29 08:43:08.719 DEBUG   http.handlers.reverse_proxy     selected upstream       {"dial": ":7886", "total_upstreams": 1}
2022/07/29 08:43:08.719 DEBUG   http.handlers.file_server       sanitized path join     {"site_root": "./assets", "request_path": "/assets//", "result": "assets/assets/"}
2022/07/29 08:43:08.719 DEBUG   http.log.error  {id=jg4sfem4f} fileserver.(*FileServer).notFound (staticfiles.go:511): HTTP 404 {"request": {"remote_ip": "127.0.0.1", "remote_port": "49628", "proto": "HTTP/1.1", "method": "GET", "host": "localhost:7885", "uri": "/assets//", "headers": {"User-Agent": ["HTTPie/1.0.3"], "Accept": ["*/*"], "Accept-Encoding": ["gzip, deflate"], "X-Forwarded-For": ["127.0.0.1"], "X-Forwarded-Host": ["localhost:7885"], "X-Forwarded-Proto": ["http"]}}, "duration": 0.000068585, "status": 404, "err_id": "jg4sfem4f", "err_trace": "fileserver.(*FileServer).notFound (staticfiles.go:511)"}
2022/07/29 08:43:08.719 DEBUG   http.handlers.reverse_proxy     upstream roundtrip      {"upstream": "{http.regexp.parts.2}:7886", "duration": 0.000454249, "request": {"remote_ip": "127.0.0.1", "remote_port": "53926", "proto": "HTTP/1.1", "method": "GET", "host": "localhost:7885", "uri": "/assets//", "headers": {"X-Forwarded-Proto": ["http"], "X-Forwarded-Host": ["localhost:7885"], "User-Agent": ["HTTPie/1.0.3"], "Accept-Encoding": ["gzip, deflate"], "Accept": ["*/*"], "X-Forwarded-For": ["127.0.0.1"]}}, "headers": {"Content-Length": ["0"], "Server": ["Caddy"], "Date": ["Fri, 29 Jul 2022 08:43:08 GMT"]}, "status": 404}

any ideas what i did wrong ?

EDIT:

Somehow i managed to partly fixed it by using this config

{
	admin 127.0.0.1:20199
	debug
	log stdout
}

:7885 {
	root * ./data

	php_fastcgi unix//var/run/php/php8.1-fpm.sock {
		@is_files header X-Accel-Redirect /_files/*

		handle_response @is_files {
			route {
				header X-Internal-Link {http.reverse_proxy.header.X-Accel-Redirect}

				rewrite * {http.reverse_proxy.header.X-Accel-Redirect}

				@parts path_regexp parts /_files/(.+?)/(.+?)/(.+?)/(.*)

				rewrite @parts /{re.parts.3}/{re.parts.4}

				reverse_proxy {re.parts.2}:7886
			}
		}
	}
}

:7886 {
	root * ./assets
	file_server
}

Yep - you need to apply the matcher to something. It doesn’t do anything on its own, it needs to be paired with a directive.

Thanks, im facing another problem now though

i keep getting dial tcp: lookup http on 127.0.0.53:53: server misbehaving trying to diagnose it

Seems like your local DNS resolver isn’t working correctly.

Thanks, there was misconfigured regex somewhere leading to http:443 instead of host:443 :slight_smile: