How to respond with html source code?

1. Output of caddy version:

v2.6.1 h1:EDqo59TyYWhXQnfde93Mmv4FJfYe00dO60zMiEt+pzo=

2. How I run Caddy:

a. System environment:

Docker on Ubuntu Server 20.04

b. Command:

docker-compose up -d

c. Service/unit/compose file:

version: "3"
services:

  caddy:
    image: caddy
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "8448:8448"
      - "9001:9001"
    environment:
      - MY_DOMAIN
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./data:/data
      - ./config:/config

networks:
  default:
    external:
      name: $DEFAULT_NETWORK

d. My complete Caddy config:

#{
#    debug
#    acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
#}

(vpn_snippet) {
        @vpn {
                remote_ip forwarded 172.22.0.1
        }
}

internal.{$MY_DOMAIN} {
        import vpn_snippet
        handle @vpn {
                reverse_proxy wordpress:80
        }
        respond "<center><h1>No Access without VPN!</h1><br>Contact the administrator for more information</center>" 403
}

3. The problem I’m having:

Up until some time ago (I guess some update) it was possible to serve a very simple html site using the respond directive. I just noticed now that this solution makes the web browser show the html code without “interpreting” it. What is the way to go now to server simple sites like this?

4. Error messages and/or full log output:

no relevant logs

5. What I already tried:

6. Links to relevant resources:

You’ll need to set the Content-Type header for the browser to interpret it as HTML.

internal.{$MY_DOMAIN} {
	import vpn_snippet
	handle @vpn {
		reverse_proxy wordpress:80
	}

	handle {
		header Content-Type text/html
		respond "<center><h1>No Access without VPN!</h1><br>Contact the administrator for more information</center>" 403
	}
}

FYI, that’s not a valid HTML document, because you don’t have an <html> tag and such. Browsers tend to accept it anyway.

A better approach would be to write the HTML to file and use file_server instead. You could also flip the matcher with not to emit an error when the request is from an unsupported host, and use handle_errors to react to it.

internal.{$MY_DOMAIN} {
	@vpn not remote_ip 172.22.0.1
	error @vpn 403

	reverse_proxy wordpress:80

	handle_errors {
		@403 `{err.status_code} == 403`
		handle @403 {
			root * /srv
			rewrite * /403.html
			file_server
		}
	}
}

Be careful with remote_ip forwarded. It should only be used if you’re running Caddy behind a proxy who you trust to correctly set X-Forwarded-For and prevent spoofing; any client could just set X-Forwarded-For on their request and bypass the check, unless the proxy in front of Caddy ignores the incoming value and sets it to the remote address.

3 Likes

This topic was automatically closed after 30 days. New replies are no longer allowed.