DOMAIN_APP environment variable. Custom Domain refers to any domain added to any blog within your Hyvor Blogs instance (e.g. personal-domain.com, blog.company.com, etc.). Each blog can have its own custom domain.
Things to consider when running Hyvor Blogs behind a reverse proxy:
TLS_MODE=external if your reverse proxy terminates TLS. In this case, app domain will only listen on port :80 and your reverse proxy will terminate TLS and forward requests to Hyvor Blogs’ port 80 over HTTP.TLS_MODE=auto if you want to let Hyvor Blogs handle TLS. In this case, your reverse proxy should forward TCP traffic to the app domain on ports 80 and 443 (port 80 is required for the ACME HTTP challenge).TLS_MODE=manual with a custom certificate and key to handle TLS between your reverse proxy and the app domain.TRUSTED_PROXIES env (by default, it includes all private IP ranges).App domain and custom domains are handled the same way in this mode. Pass all TCP traffic on port 443 straight through to Hyvor Blogs, and forward port 80 for the ACME HTTP challenge:
labels:
# forward TCP on port 443 (any domain)
- 'traefik.tcp.routers.hyvor-blogs.rule=HostSNI(`*`)'
- 'traefik.tcp.routers.hyvor-blogs.entrypoints=https'
- 'traefik.tcp.routers.hyvor-blogs.tls.passthrough=true'
- 'traefik.tcp.routers.hyvor-blogs.service=hyvor-blogs'
- 'traefik.tcp.services.hyvor-blogs.loadbalancer.server.port=443'
# forward HTTP on port 80 (any domain)
- 'traefik.http.routers.hyvor-blogs.rule=Host(`*`)'
- 'traefik.http.routers.hyvor-blogs.entrypoints=http'
- 'traefik.http.routers.hyvor-blogs.service=hyvor-blogs'
- 'traefik.http.services.hyvor-blogs.loadbalancer.server.port=80'Traefik terminates TLS for the app domain itself, while custom domains still need raw TCP passthrough on port 443:
labels:
# app domain: terminate TLS at Traefik
- 'traefik.http.routers.hyvor-blogs.rule=Host(`${DOMAIN_APP}`)'
- 'traefik.http.routers.hyvor-blogs.entrypoints=https'
- 'traefik.http.routers.hyvor-blogs.tls.certresolver=le'
- 'traefik.http.routers.hyvor-blogs.service=hyvor-blogs'
- 'traefik.http.services.hyvor-blogs.loadbalancer.server.port=80'
## IMPORTANT!
## This only works with `--certificatesresolvers.le.acme.tlschallenge=true` traefik setting
## httpchallenge will not work
## because it interferes with custom domain HTTP challenge since
## Traefik reserves /.well-known/acme-challenge for itself.
# custom domains: pass TCP on port 443 straight through
- 'traefik.tcp.routers.custom-domain-https.rule=HostSNI(`*`)'
- 'traefik.tcp.routers.custom-domain-https.entrypoints=https'
- 'traefik.tcp.routers.custom-domain-https.tls.passthrough=true'
- 'traefik.tcp.routers.custom-domain-https.service=hyvor-blogs-custom-domain'
- 'traefik.tcp.services.hyvor-blogs-custom-domain.loadbalancer.server.port=443'
# forward HTTP on port 80 (any domain, incl. ACME HTTP challenge for custom domains)
- 'traefik.http.routers.custom-domain-http.rule=Host(`*`)'
- 'traefik.http.routers.custom-domain-http.entrypoints=http'
- 'traefik.http.routers.custom-domain-http.service=hyvor-blogs-custom-domain'
- 'traefik.http.services.hyvor-blogs-custom-domain.loadbalancer.server.port=80'Regardless of domain, port 80 is always plain HTTP, so a normal reverse_proxy works for it. Port 443 is the tricky part: custom domains (and the app domain, if TLS_MODE=auto) need to reach Hyvor Blogs’ own TLS termination completely untouched, which Caddy cannot do out of the box. It requires the layer4 plugin, built into Caddy with xcaddy:
xcaddy build --with github.com/mholt/caddy-l4App domain and custom domains are handled the same way in this mode. Pass all TCP traffic on port 443 straight through to Hyvor Blogs, and forward port 80 for the ACME HTTP challenge:
{
layer4 {
:443 {
route {
proxy tcp/hyvor-blogs:443
}
}
}
}
:80 {
reverse_proxy hyvor-blogs:80
}Here Caddy terminates TLS for the app domain itself, while custom domains still need raw passthrough (their certificates are always issued by Hyvor Blogs). Since only one listener can bind :443, layer4 matches on SNI first: the app domain is forwarded internally to a normal Caddy site bound to a loopback port for real TLS termination, everything else is passed through untouched:
{
layer4 {
:443 {
@app tls sni ${DOMAIN_APP}
route @app {
proxy tcp/127.0.0.1:8443
}
route {
proxy tcp/hyvor-blogs:443
}
}
}
}
:80 {
reverse_proxy hyvor-blogs:80
}
{$DOMAIN_APP}:8443 {
reverse_proxy hyvor-blogs:80 {
header_up X-Forwarded-Proto https
}
}NGINX needs its stream module (built with --with-stream and --with-stream_ssl_preread_module, which the official nginx Docker image includes) to pass port 443 through without terminating TLS. Port 80 is plain HTTP and is reverse proxied normally in the regular http block.
In this mode Hyvor Blogs terminates TLS for every domain, so stream just needs to preread the SNI and pass the connection straight through, without branching on which domain it is:
stream {
server {
listen 443;
proxy_pass hyvor-blogs:443;
ssl_preread on;
}
}
http {
server {
listen 80 default_server;
server_name _;
location / {
proxy_pass http://hyvor-blogs:80;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}Here NGINX terminates TLS for the app domain (replace blogs.example.com below with your DOMAIN_APP), while custom domains are still passed through as raw TCP, since their certificates are always issued by Hyvor Blogs:
stream {
map $ssl_preread_server_name $backend_443 {
blogs.example.com 127.0.0.1:8443; # your app domain
default hyvor-blogs:443;
}
server {
listen 443;
proxy_pass $backend_443;
ssl_preread on;
}
}
http {
server {
listen 80 default_server;
server_name _;
location / {
proxy_pass http://hyvor-blogs:80;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
server {
listen 127.0.0.1:8443 ssl;
server_name blogs.example.com;
ssl_certificate /etc/letsencrypt/live/blogs.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/blogs.example.com/privkey.pem;
location / {
proxy_pass http://hyvor-blogs:80;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}