Home Assistant Add-on: Caddy 2

Unfortunately I don’t have complete control over the network where I’m at. I’ve asked for port 80 to be forwarded to 443 on the machine running caddy 1. I’m just confirming with them they have done that.

Caddy one should work using korylprinces add on still but It sounds like I should move on to Caddy 2 anyways. Its always been a bit of trial and error with me anyways and I can’t really do that without access to the network hardware.

Thanks all for the feedback. Covid 19 has made my living circumstances unusual so I may have to give up on things for a bit I fear

You need to forward port 80 to port 80 with http validation. 80 to 443 won’t work. I would strongly recommend using DNS validation and not http validation - then you won’t require any port forwards. I don’t know if it is still possible to build v1 caddy with the correct addons anymore automatically. Caddy 2 you can build easily with the dns addons. Let me know if you need any help as I recently went through the conversion.

Ugh I will have to reteach myself the whole networking situation here. I thought that forward 80 to 443 was to redirect requests made on http to https, so for instance people requesting http://ombi.redacted.duckdns.org would be redirected to https://ombi.redacted.duckdns.org
I’m trying to remember exactly how I set up my router here and passing along requests to somebody who doesn’t necessarily get back to me all the time to implement things on their fancy ubiquiti router. I dont want to waste anybody’s time here so I’ll see if I can find another tutorial that includes setting this up in terms of this and also google assistant I had running with HA. It looks like the caddy one addon by Koryl might not work anymore so when I do get started Ill move to caddy 2. Incidentally I think I got the too many requests immediately after I started the old addon but not sure what that could mean. Any ways thanks for pointing me in the right direction

Google Assistant works fine with Caddy1 but it requires you to use a https URL. Caddy will convert to https by default BTW. There is no reason for caddy1 not to be working. I only converted to Caddy2 myself a few weeks ago.
But letsencrypt for either addon will require port 80-80 forwarded if you use http validation (the default) so in your situation I would use dns validation instead (actually I’d use that anyway) it’s really quite easy to setup and use.

1 Like

I just need to revisit my Caddyfile.

I am having a few issues with the conversion from caddy1 to caddy2

First, in Caddy1 I did this:

    proxy / localhost:8123 {
        websocket
        transparent
        header_upstream Authorization {>Authorization}
    }

I know websocket and transparent are not needed but if I enable

header_up Authorization {>Authorization}

in Caddy2 then I can not access any ingress panel (like supervisor etc)

Secondly, NONE of my iFrames work and I get a console error like : Refused to display ‘https://subdomain.domain/login’ in a frame because it set ‘X-Frame-Options’ to ‘sameorigin’.
My header is like this:

        header {
                Strict-Transport-Security "max-age=31536000; includeSubdomains"
                X-XSS-Protection "1; mode=block"
                X-Content-Type-Options "nosniff"
                X-Frame-Options "SAMEORIGIN"
                Referrer-Policy "same-origin"
                -Server
        }

Worked perfectly in Caddy1 but error above for any iFrame with Caddy2

Any ideas?
I also posted on the Caddy2 forum but they are not necessarily too helpful

Hi @dasbooter,
Although outdated, don’t hesitate to refesh your memory on the general instructions for a Caddy (1) usage here:

Note to myself: Update that page…

Important for you, in case you want to go with the default SSL method:
Forward both ports (80 and 443) from your router to your Hass.io server. Can’t help you with that part, as it is highly dependent on the manufacturer etc. Just ensure that the ports are mapped 80:80 and 443:443 as from:to
Once the ports arrive at home assistant, the most dificult part is done. Run the add-on with the non-config settings and you should be able to access HA via https.
As to your question, Caddy will take care of the routing from 80 to 443 automatically.

As mentioned by @DavidFW1960, there is another method for the Cert verification, which may require only one port. However, I am not used to that method but I am certain David will be able to help if wanted.

Hi @DavidFW1960,
as to your issue, a few questions:

  1. Why do you change the Authorization? What is the purpose of that header_up option?
  2. The behavior of your iFrames seems to match your config. As it says, X-Frame-Options are blocked to SAMEORIGIN. Did you try different settings? Please also check your http setting in HA core. Have a look for especially this option: https://www.home-assistant.io/integrations/http/#use_x_forwarded_for

As I said I think I changed it because when I did a security check on my HA from a third party site it recommended that option.
Yes I have x-forwarded-for set. Setting x-frame-options to same origin should ALLOW iFrame connections not deny them.

Caddy1 had no issues with those options and worked correctly.

So as a follow-up. I was recommended to try a content-security-policy like this:

Content-Security-Policy "frame-ancestors domain:xxxxx *.domain:xxxxx"

In the header instead and that works with my iframes.

When using the X-Frame-Options with “same-origin”, it failed on my end.
But changing the X-Frame-Options to the following enabled my iFrame instantly:

X-Frame-Options "ALLOW-FROM https://my.domain.com"

The above allows your webpage to be inserted into iFrames, but only on that particular page.
See also: here

The content-security policy supersedes the x-frame-options. A poster on the Caddy forum linked me to a doc about it. When I do a security check on my HA even though I slated the x-frame-options it shows that requirement as satisfied now. I can give you some links tomorrow when on my PC if you are interested.

I still don’t know it is only caddy2 that didn’t like the sameorigin… caddy1 was fine with that…
From your link

SAMEORIGIN which allows you to frame your own site or ALLOW-FROM https://example.com/ which lets you specify sites that are permitted to frame your own site.

I don’t understand unless sameorigin doesn’t see a sub domain as same origin in Caddy2?

From how it looks and sounds, Caddy 2 seems to be stricter. Which I think could be explained as a secure default. Who knows. At least I don’t know.

Regarding the content-security policy, that’s a header I am struggling with to be honest. Either it is secure, or part of my web pages aren’t loaded, even if I allow any source. If you could share some good links, or even your setting for that header, that would be awesome!

Yeah for sure. We need all the help we can get sometimes.
This is my Caddyfile

{
        email [email protected]
}

(common) {
        tls {
                dns lego_deprecated namecheap
                on_demand
        }
        header {
                Strict-Transport-Security "max-age=31536000; includeSubdomains"
                X-XSS-Protection "1; mode=block"
                X-Content-Type-Options "nosniff"
                Referrer-Policy "same-origin"
				Content-Security-Policy "frame-ancestors my-domain.com:xxxxx *.my-domain.com:xxxxx"
                -Server
        }
}

my-domain.com:xxxxx {
        import common
        reverse_proxy localhost:8123 {
        }
}

cockpit.my-domain.com:xxxxx {
        import common
        reverse_proxy localhost:19090 {
        }
}

glances.my-domain.com:xxxxx {
        import common
        reverse_proxy localhost:61208 {
        }
}

logviewer.my-domain.com:xxxxx {
        import common
        reverse_proxy localhost:4277 {
        }
}

portainer.my-domain.com:xxxxx {
        import common
        reverse_proxy localhost:9000 {
        }
}

tasmoadmin.my-domain.com:xxxxx {
        import common
        reverse_proxy localhost:9541 {
        }
}

My Caddy2 config:

non_caddyfile_config:
  email: '!secret le_email'
  domain: '!secret ha_host'
  destination: localhost
  port: 8123
args:
  - '--watch'
env_vars:
  - name: NAMECHEAP_API_USER
    value: '!secret name_user_V2'
  - name: NAMECHEAP_API_KEY
    value: '!secret name_key_V2'
log_level: info
config_path: /share/caddy2/Caddyfile
custom_binary_path: /share/caddy2/caddy

See my caddyforum post here https://caddy.community/t/converting-caddyfile-from-v1-to-v2-xframe-issue/11462/4?u=davidfw1960 where he says preferred method now is to use content security policy instead of x-frame-options.
Then if I do a scan here https://securityheaders.com/
I get this result:

Note I had 2 entries for content security policy.
If I remove my-domain.com:xxxxx ingress frames won’t load
If I remove *.my-domain.com:xxxxx iframes won’t load

See also I defined a different path for Caddy2 as I had Caddy1 as well when I was playing and never switched back. I use a cudtom caddy that includes the lego_deprecated plugin for dns validation.

On my router I have only one port opened for HA - it is a high number non standard port represented as xxxxx above. NO ports are opened or required by Caddy to obtain ssl certificates from LetsEncrypt - the beauty of DNS validation instead of http validation.

All of this really is childs play to setup. It’s the tweaking that gets you but I am only tweaking header options now and checking I still have access correctly without leaving too many holes. I am going to play with the permissions-policy now but this is now becoming a movable target as I see a few new header options are coming soon as well…

I just added a Permissions policy

Permissions-Policy "geolocation=(self domain:xxxxx *.domain:xxxxx), microphone=()"

And now get A+ rating…
(Also for completeness, I am downloading the Caddy Binary from the official Caddy2 site. I build it for AMD64 with the lego-deprecated plugin and it goes into the Cadd2 folder as caddy. I think I had to make it executable as well. The addon log will then tell you you are using a custom binary.

Caddy2 is pretty cool. At least it makes sense in my mind how it works which I could never work out with Nginx (vaguaries of the header not withstanding)

Hi @DavidFW1960,
Many thanks for sharing your setup! It is very interesting to see how other people use their Caddy :slight_smile:
And guess what, not only my setup, but also my experience is close to exactly the same! Setting up all of this with Nginx would be a nightmare…

But coming to some of my findings:

  1. Header
    I am using nearly the same header. Mixing your and my header, I get the following result, which passes A+ at securityheaders.com:
(header) {
	header {
		Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
		X-XSS-Protection "1; mode=block"
		X-Content-Type-Options "nosniff"
		Referrer-Policy "same-origin"
		-Server
		Content-Security-Policy "frame-ancestors domain.com *.domain.com"
		Permissions-Policy "geolocation=(self domain.com *.domain.com)"
	}
}
  1. Your Caddy2 config
    You can skip the non_caddyfile_config, as you make use of a Caddyfile. When the add-on finds a Caddyfile, the non_caddyfile_config is ignored. Therefore:
non_caddyfile_config: {}
args:
  - '--watch'
env_vars:
  - name: NAMECHEAP_API_USER
    value: '!secret name_user_V2'
  - name: NAMECHEAP_API_KEY
    value: '!secret name_key_V2'
log_level: info
config_path: /share/caddy2/Caddyfile
custom_binary_path: /share/caddy2/caddy
  1. Upcoming header
    I am also watching the upcoming headers, always trying to keep my server as secure as possible. :slight_smile:

Last but not least, thanks for sharing your setup for the DNS cert method, avoiding HTTP and HTTPS ports altogether. I’ll stick to the regular method though, until my DNS provider is supported.

Whenever you find a suitable or security update for our setup, don’t hesitate to share it!

3 Likes

Ah I was wondering about the non Caddyfile config. I will remove it. Thanks.

Also using lego_deprecated works fine so if your dns provider is supported there I would use it in preference to opening ports (particularly 80 and 443!)

I also use a non-standard port as shown and additionally my domain only has an AAAA record so I am pretty well obscured from view as well. This is only a nuisance if I am on a mobile network that doesn’t support IPv6 but then I can just use ZeroTier or even just my VPN to the router and I’m good. Fortunately my mobile provider and ISP both use IPv6 dual stack. Caddy works perfectly with this setup.

Not going to lie, I’ve been following these post for quite a bit of time on the HA site describing the caddy v2 upgrade and it still has me scratching my head. I’ve got errors with misconfigured set ups such as 403 and 400 and now a bind issue that kicks me right out. could someone help to diagnose?

caddy2 log:

Open source web and proxy server with automatic HTTPS
-----------------------------------------------------------
 Add-on version: 0.3.0
 You are running the latest version of this add-on.
 System: Home Assistant OS 5.11  (amd64 / qemux86-64)
 Home Assistant Core: 2021.2.3
 Home Assistant Supervisor: 2021.02.9
-----------------------------------------------------------
 Please, share the above information when looking for help
 or support in, e.g., GitHub, forums or the Discord chat.
-----------------------------------------------------------
[cont-init.d] 00-banner.sh: exited 0.
[cont-init.d] 01-log-level.sh: executing... 
Log level is set to INFO
[cont-init.d] 01-log-level.sh: exited 0.
[cont-init.d] done.
[services.d] starting services
[services.d] done.
INFO: Starting Caddy...
INFO: Setting DUCKDNS_TOKEN to <token>
INFO: Found custom Caddy at /share/caddy/caddy
v2.3.0 h1:fnrqJLa3G5vfxcxmOH/+kJOcunPLhSBnjgIvjXV/QTA=
INFO: Caddyfile found at /share/caddy/Caddyfile
{"level":"info","ts":1613411303.8377643,"msg":"using provided configuration","config_file":"/share/caddy/Caddyfile","config_adapter":""}
{"level":"info","ts":1613411303.8417926,"logger":"admin","msg":"admin endpoint started","address":"tcp/localhost:2019","enforce_origin":false,"origins":["localhost:2019","[::1]:2019","127.0.0.1:2019"]}
{"level":"info","ts":1613411303.8432853,"logger":"http","msg":"enabling automatic HTTP->HTTPS redirects","server_name":"srv0"}
run: loading initial config: loading new config: http app module: start: tcp: listening on :8123: listen tcp :8123: bind: address already in use
[cont-finish.d] executing container finish scripts...
[cont-finish.d] 99-message.sh: executing... 
[cont-finish.d] 99-message.sh: exited 0.
[cont-finish.d] done.
[s6-finish] waiting for services.
[s6-finish] sending all processes the TERM signal.

my caddy add on config looks like this:

non_caddyfile_config:
  email: <my-email>@email.com
  domain: <my-domain>.duckdns.org
  destination: localhost
  port: 8123
args: []
env_vars:
  - name: DUCKDNS_TOKEN
    value: <token>
log_level: info

My Caddyfile looks like this:

{   
	email <my-email>@email.com
}

<my-domain>.duckdns.org:8123 {
    tls {
        dns lego_deprecated duckdns
        on_demand
	}
    header {
		Strict-Transport-Security "max-age=31536000; includeSubdomains"
		X-XSS-Protection "1; mode=block"
		X-Content-Type-Options "nosniff"
		X-Frame-Options "SAMEORIGIN"
		Referrer-Policy "same-origin"
		-Server
	}
    reverse_proxy localhost:8123
}

Also my config.yaml http section:

http:
  # Uncomment this to add a password (recommended!)
  # api_password: !secret http_password
  # ssl_certificate: /ssl/fullchain.pem
  # ssl_key: /ssl/privkey.pem
  use_x_forwarded_for: true
  trusted_proxies:
    - 127.0.0.1
    - ::1
  ip_ban_enabled: True
  login_attempts_threshold: 5
  # Uncomment this if you are using SSL/TLS, running in Docker container, etc.
  base_url: https://<my-domain>.duckdns.org:8123

Any help would be appreciated.

First thing I notice is it’s not using a custom caddy so the standard caddy doesn’t include the Duckdns addon. Not 100% sure but I think there is a read duckdns addon so you shouldn’t need deprecated anyway. In any case you need to build a custom caddy and copy the binary to the caddy directory as caddy with no extension and also make sure it’s executable.

Oops… Missed you are using a custom one.

I think the issue is way simpler… You are proxying 8123 to 8123…
run: loading initial config: loading new config: http app module: start: tcp: listening on :8123: listen tcp :8123: bind: address already in use. Which port are you forwarding in your router?

Your baseurl is ok. I would just remove the 8123 from here:

<my-domain>.duckdns.org: {
    tls {
        dns lego_deprecated duckdns
        on_demand
	}

and just forward 443 to 443 in your router. Then any https connection to your domain will get proxied to 8123. Personally I prefer to use a different port than 8123 externally hence my config and I would then forward that port xxxxx to xxxxx in your router and change the baseurl to reflect that new port.

I am seeing browser console errors with this… you?

Permissions-Policy “geolocation=(self), microphone=()”
everything seems to work still

Hi David, I appreciate your pointers - I’ve made the adjustment to the Caddyfile, but now it seems to hang at these portions of the certificate issuing, and then the add-on just resets:

{"level":"info","ts":1613444519.7058322,"msg":"using provided configuration","config_file":"/share/caddy/Caddyfile","config_adapter":""}
{"level":"info","ts":1613444519.7090364,"logger":"admin","msg":"admin endpoint started","address":"tcp/localhost:2019","enforce_origin":false,"origins":["localhost:2019","[::1]:2019","127.0.0.1:2019"]}
{"level":"info","ts":1613444519.7096941,"logger":"http","msg":"server is listening only on the HTTPS port but has no TLS connection policies; adding one to enable TLS","server_name":"srv0","https_port":443}
{"level":"info","ts":1613444519.7099156,"logger":"http","msg":"enabling automatic HTTP->HTTPS redirects","server_name":"srv0"}
{"level":"info","ts":1613444519.7106404,"logger":"http","msg":"enabling automatic TLS certificate management","domains":["<my-domain>.duckdns.org"]}
{"level":"info","ts":1613444519.71106,"msg":"autosaved config","file":"/data/caddy/autosave.json"}
{"level":"info","ts":1613444519.711295,"msg":"serving initial configuration"}
{"level":"info","ts":1613444519.7121103,"logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0xc0000eaaf0"}
{"level":"info","ts":1613444519.7130206,"logger":"tls","msg":"cleaned up storage units"}

What is the meaning of the “has no tls policies”?

Thank you in advance!