HTTP/3 not used for Nextcloud and Firefox

1. The problem I’m having:

I try to get HTTP/3 to work with my Nextcloud instance, but for some reason Firefox keeps using HTTP/2.
Unfortunately, I cannot recreate this using curl, since it doesn’t support HTTP/3 at the moment (at least not the version that I am using).
So i can only show you the Firefox screenshots for now:

Of course, I opened port 443 UDP on my router and the Docker container, but still no luck.

And while I am writing this question I came up with the idea of trying Chromium as well, and apparently here Http/3 is used indeed.
So now my question is more like, why does Firefox not use HTTP/3 if it seems to be available?

2. Error messages and/or full log output:

I get no specific error, but I can still show you the logs.
For some reason the forums do not allow me to post the logs, so i put them on Pastebin: NF INF INF INF INF INF INF INF ts=1704614818.4108605 INF INF INF INF INF INF INF - Pastebin.com

I had to truncate it, because every request contains a lot of private information because of the filenames.
If you need more logs, I can provide them but it takes time to clean them.

3. Caddy version:

v2.7.6 h1:w0NymbG2m9PcvKWsrXO6EEkY9Ru4FJK8uQbYcev1p3A=

4. How I installed and ran Caddy:

a. System environment:

Docker on Ubuntu 22.04 which itself runs in a VM hosted by TrueNAS Scale

b. Command:

caddy run --config /etc/caddy/Caddyfile --adapter caddyfile

c. Service/unit/compose file:

I do not use compose for this container, but i can give you the output of docker inspect.

{
    "AppArmorProfile": "docker-default",
    "Args": [
        "run",
        "--config",
        "/etc/caddy/Caddyfile",
        "--adapter",
        "caddyfile"
    ],
    "Config": {
        "AttachStderr": false,
        "AttachStdin": false,
        "AttachStdout": false,
        "Cmd": [
            "caddy",
            "run",
            "--config",
            "/etc/caddy/Caddyfile",
            "--adapter",
            "caddyfile"
        ],
        "Domainname": "",
        "Entrypoint": null,
        "Env": [
            "CADDY_DIST_COMMIT=80870b227ded910971ecace4a0c136bf0ef46342",
            "CADDY_VERSION=v2.1.1",
            "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
            "XDG_CONFIG_HOME=/config",
            "XDG_DATA_HOME=/data"
        ],
        "ExposedPorts": {
            "2019/tcp": {},
            "443/tcp": {},
            "443/udp": {},
            "80/tcp": {},
            "8071/tcp": {}
        },
        "Hostname": "c323a69d3f0e",
        "Image": "caddy:2",
        "Labels": {
            "org.opencontainers.image.description": "a powerful, enterprise-ready, open source web server with automatic HTTPS written in Go",
            "org.opencontainers.image.documentation": "https://caddyserver.com/docs",
            "org.opencontainers.image.licenses": "Apache-2.0",
            "org.opencontainers.image.source": "https://github.com/caddyserver/caddy-docker",
            "org.opencontainers.image.title": "Caddy",
            "org.opencontainers.image.url": "https://caddyserver.com",
            "org.opencontainers.image.vendor": "Light Code Labs",
            "org.opencontainers.image.version": "v2.1.1"
        },
        "OnBuild": null,
        "OpenStdin": false,
        "StdinOnce": false,
        "Tty": false,
        "User": "1100:1100",
        "Volumes": {
            "/config": {},
            "/data": {},
            "/etc/caddy/Caddyfile": {},
            "/run/nextcloud": {},
            "/run/notify_push": {},
            "/var/www/html": {}
        },
        "WorkingDir": "/srv"
    },
    "Created": "2023-12-18T07:37:31.326447414Z",
    "Driver": "overlay2",
    "ExecIDs": [
        "8e8603737427447890a73bd2928a6b8acc77658a108b3545107253e65ba780b7"
    ],
    "GraphDriver": {
        "Data": {
            "LowerDir": "/var/lib/docker/overlay2/75b5bc48c8a1f20ce9e890f997e8d9c0078de6a5ba866612fefdae14eaebee8d-init/diff:/var/lib/docker/overlay2/bcb4964be788be4f24e9f2e92f676483f9294a6b9c4449663d6f831c203d74b1/diff:/var/lib/docker/overlay2/4cc80203fe51c0f30a96c7cee0a1aba76b9eb90b48ec046bf977d78b28f03f5c/diff:/var/lib/docker/overlay2/1386c1521d0389c0c572d180c9a1ff9a4e2d750c1d9e2859f74076e94f2d6c6f/diff:/var/lib/docker/overlay2/3202333ec3d80d387e23112eef927e931070bc77849ba9a4f8331a81e3f438be/diff",
            "MergedDir": "/var/lib/docker/overlay2/75b5bc48c8a1f20ce9e890f997e8d9c0078de6a5ba866612fefdae14eaebee8d/merged",
            "UpperDir": "/var/lib/docker/overlay2/75b5bc48c8a1f20ce9e890f997e8d9c0078de6a5ba866612fefdae14eaebee8d/diff",
            "WorkDir": "/var/lib/docker/overlay2/75b5bc48c8a1f20ce9e890f997e8d9c0078de6a5ba866612fefdae14eaebee8d/work"
        },
        "Name": "overlay2"
    },
    "HostConfig": {
        "AutoRemove": false,
        "Binds": [
            "/nfs/freenas/caddy/caddy2-config:/config",
            "/nfs/freenas/caddy/caddy2-data:/data",
            "/nfs/freenas/caddy/Caddyfile2:/etc/caddy/Caddyfile",
            "/nfs/freenas/ncfpm:/var/www/html:ro",
            "/nfs/freenas/caddy/nextcloud-unix-socket:/run/nextcloud",
            "/nfs/freenas/caddy/notify-push-unix-socket:/run/notify_push"
        ],
        "BlkioDeviceReadBps": null,
        "BlkioDeviceReadIOps": null,
        "BlkioDeviceWriteBps": null,
        "BlkioDeviceWriteIOps": null,
        "BlkioWeight": 0,
        "BlkioWeightDevice": null,
        "CapAdd": [
            "AUDIT_WRITE",
            "CHOWN",
            "DAC_OVERRIDE",
            "FOWNER",
            "FSETID",
            "KILL",
            "MKNOD",
            "NET_BIND_SERVICE",
            "NET_RAW",
            "SETFCAP",
            "SETGID",
            "SETPCAP",
            "SETUID",
            "SYS_CHROOT"
        ],
        "CapDrop": [
            "AUDIT_CONTROL",
            "BLOCK_SUSPEND",
            "DAC_READ_SEARCH",
            "IPC_LOCK",
            "IPC_OWNER",
            "LEASE",
            "LINUX_IMMUTABLE",
            "MAC_ADMIN",
            "MAC_OVERRIDE",
            "NET_ADMIN",
            "NET_BROADCAST",
            "SYSLOG",
            "SYS_ADMIN",
            "SYS_BOOT",
            "SYS_MODULE",
            "SYS_NICE",
            "SYS_PACCT",
            "SYS_PTRACE",
            "SYS_RAWIO",
            "SYS_RESOURCE",
            "SYS_TIME",
            "SYS_TTY_CONFIG",
            "WAKE_ALARM"
        ],
        "Cgroup": "",
        "CgroupParent": "",
        "CgroupnsMode": "host",
        "ConsoleSize": [
            0,
            0
        ],
        "ContainerIDFile": "",
        "CpuCount": 0,
        "CpuPercent": 0,
        "CpuPeriod": 0,
        "CpuQuota": 0,
        "CpuRealtimePeriod": 0,
        "CpuRealtimeRuntime": 0,
        "CpuShares": 0,
        "CpusetCpus": "",
        "CpusetMems": "",
        "DeviceCgroupRules": null,
        "DeviceRequests": null,
        "Devices": [],
        "Dns": [],
        "DnsOptions": [],
        "DnsSearch": [],
        "ExtraHosts": [],
        "GroupAdd": null,
        "IOMaximumBandwidth": 0,
        "IOMaximumIOps": 0,
        "IpcMode": "private",
        "Isolation": "",
        "Links": null,
        "LogConfig": {
            "Config": {
                "max-file": "3",
                "max-size": "10m"
            },
            "Type": "local"
        },
        "MaskedPaths": [
            "/proc/asound",
            "/proc/acpi",
            "/proc/kcore",
            "/proc/keys",
            "/proc/latency_stats",
            "/proc/timer_list",
            "/proc/timer_stats",
            "/proc/sched_debug",
            "/proc/scsi",
            "/sys/firmware"
        ],
        "Memory": 0,
        "MemoryReservation": 0,
        "MemorySwap": 0,
        "MemorySwappiness": null,
        "NanoCpus": 0,
        "NetworkMode": "bridge",
        "OomKillDisable": null,
        "OomScoreAdj": 0,
        "PidMode": "",
        "PidsLimit": null,
        "PortBindings": {
            "443/tcp": [
                {
                    "HostIp": "",
                    "HostPort": "443"
                }
            ],
            "443/udp": [
                {
                    "HostIp": "",
                    "HostPort": "443"
                }
            ],
            "80/tcp": [
                {
                    "HostIp": "",
                    "HostPort": "80"
                }
            ],
            "8071/tcp": [
                {
                    "HostIp": "",
                    "HostPort": "8071"
                }
            ]
        },
        "Privileged": false,
        "PublishAllPorts": false,
        "ReadonlyPaths": [
            "/proc/bus",
            "/proc/fs",
            "/proc/irq",
            "/proc/sys",
            "/proc/sysrq-trigger"
        ],
        "ReadonlyRootfs": false,
        "RestartPolicy": {
            "MaximumRetryCount": 0,
            "Name": "unless-stopped"
        },
        "Runtime": "runc",
        "SecurityOpt": null,
        "ShmSize": 67108864,
        "UTSMode": "",
        "Ulimits": null,
        "UsernsMode": "",
        "VolumeDriver": "",
        "VolumesFrom": null
    },
    "HostnamePath": "/var/lib/docker/containers/d8c577fc18b1f6a3f23684121b1b280e3b3ab25cef1c0be9af86d8e87a62f4b4/hostname",
    "HostsPath": "/var/lib/docker/containers/d8c577fc18b1f6a3f23684121b1b280e3b3ab25cef1c0be9af86d8e87a62f4b4/hosts",
    "Id": "d8c577fc18b1f6a3f23684121b1b280e3b3ab25cef1c0be9af86d8e87a62f4b4",
    "Image": "sha256:657b947906e764d7ff65a638b958cb7f84b3754e49ede071620658385e743168",
    "LogPath": "",
    "MountLabel": "",
    "Mounts": [
        {
            "Destination": "/config",
            "Mode": "",
            "Propagation": "rprivate",
            "RW": true,
            "Source": "/nfs/freenas/caddy/caddy2-config",
            "Type": "bind"
        },
        {
            "Destination": "/data",
            "Mode": "",
            "Propagation": "rprivate",
            "RW": true,
            "Source": "/nfs/freenas/caddy/caddy2-data",
            "Type": "bind"
        },
        {
            "Destination": "/etc/caddy/Caddyfile",
            "Mode": "",
            "Propagation": "rprivate",
            "RW": true,
            "Source": "/nfs/freenas/caddy/Caddyfile2",
            "Type": "bind"
        },
        {
            "Destination": "/run/nextcloud",
            "Mode": "",
            "Propagation": "rprivate",
            "RW": true,
            "Source": "/nfs/freenas/caddy/nextcloud-unix-socket",
            "Type": "bind"
        },
        {
            "Destination": "/run/notify_push",
            "Mode": "",
            "Propagation": "rprivate",
            "RW": true,
            "Source": "/nfs/freenas/caddy/notify-push-unix-socket",
            "Type": "bind"
        },
        {
            "Destination": "/var/www/html",
            "Mode": "ro",
            "Propagation": "rprivate",
            "RW": false,
            "Source": "/nfs/freenas/ncfpm",
            "Type": "bind"
        }
    ],
    "Name": "/Caddy2",
    "NetworkSettings": {
        "Bridge": "",
        "EndpointID": "4247a740137669dece13846f8513f1d427353d3bb1f350689ef260743417e413",
        "Gateway": "172.17.0.1",
        "GlobalIPv6Address": "fd00::1:242:ac11:4",
        "GlobalIPv6PrefixLen": 80,
        "HairpinMode": false,
        "IPAddress": "172.17.0.4",
        "IPPrefixLen": 16,
        "IPv6Gateway": "fd00::1:0:0:1",
        "LinkLocalIPv6Address": "",
        "LinkLocalIPv6PrefixLen": 0,
        "MacAddress": "02:42:ac:11:00:04",
        "Networks": {
            "bridge": {
                "Aliases": null,
                "DriverOpts": null,
                "EndpointID": "4247a740137669dece13846f8513f1d427353d3bb1f350689ef260743417e413",
                "Gateway": "172.17.0.1",
                "GlobalIPv6Address": "fd00::1:242:ac11:4",
                "GlobalIPv6PrefixLen": 80,
                "IPAMConfig": {},
                "IPAddress": "172.17.0.4",
                "IPPrefixLen": 16,
                "IPv6Gateway": "fd00::1:0:0:1",
                "Links": null,
                "MacAddress": "02:42:ac:11:00:04",
                "NetworkID": "0d4881f45cf74595942670be4672f8885f8097043b842fb960d98491dc1040df"
            }
        },
        "Ports": {
            "2019/tcp": null,
            "443/tcp": [
                {
                    "HostIp": "0.0.0.0",
                    "HostPort": "443"
                },
                {
                    "HostIp": "::",
                    "HostPort": "443"
                }
            ],
            "443/udp": [
                {
                    "HostIp": "0.0.0.0",
                    "HostPort": "443"
                },
                {
                    "HostIp": "::",
                    "HostPort": "443"
                }
            ],
            "80/tcp": [
                {
                    "HostIp": "0.0.0.0",
                    "HostPort": "80"
                },
                {
                    "HostIp": "::",
                    "HostPort": "80"
                }
            ],
            "8071/tcp": [
                {
                    "HostIp": "0.0.0.0",
                    "HostPort": "8071"
                },
                {
                    "HostIp": "::",
                    "HostPort": "8071"
                }
            ]
        },
        "SandboxID": "ff033d59cd042ab7d9d3ceee0a1504ec3088c80eae4dca3fd135c354595bfe3c",
        "SandboxKey": "/var/run/docker/netns/ff033d59cd04",
        "SecondaryIPAddresses": null,
        "SecondaryIPv6Addresses": null
    },
    "Path": "caddy",
    "Platform": "linux",
    "Portainer": {
        "ResourceControl": {
            "Id": 363,
            "ResourceId": "d8c577fc18b1f6a3f23684121b1b280e3b3ab25cef1c0be9af86d8e87a62f4b4",
            "SubResourceIds": [],
            "Type": 1,
            "UserAccesses": [],
            "TeamAccesses": [],
            "Public": false,
            "AdministratorsOnly": true,
            "System": false
        }
    },
    "ProcessLabel": "",
    "ResolvConfPath": "/var/lib/docker/containers/d8c577fc18b1f6a3f23684121b1b280e3b3ab25cef1c0be9af86d8e87a62f4b4/resolv.conf",
    "RestartCount": 0,
    "State": {
        "Dead": false,
        "Error": "",
        "ExitCode": 0,
        "FinishedAt": "2024-01-07T08:06:57.293128243Z",
        "OOMKilled": false,
        "Paused": false,
        "Pid": 1345873,
        "Restarting": false,
        "Running": true,
        "StartedAt": "2024-01-07T08:06:58.154325036Z",
        "Status": "running"
    }
}

d. My complete Caddy config:

# Global settings
{
        log default {
                format console {
                        time_format wall
                }
        }
        debug
}

nc.example.com {
        encode zstd gzip
        root * /var/www/html

        # Rules here are all from .htaccess
        redir /.well-known/carddav /remote.php/dav 301
        redir /.well-known/caldav /remote.php/dav 301
        redir /.well-known/* /index.php{uri} 301 # Nextcloud front-controller handles routes to /.well-known
        redir /remote/* /remote.php{uri} 301

        # Secure headers, all from .htaccess except Permissions-Policy, STS and X-Powered-By
        header {
                Strict-Transport-Security max-age=31536000
                Permissions-Policy interest-cohort=()
                X-Content-Type-Options nosniff
                X-Frame-Options SAMEORIGIN
                Referrer-Policy no-referrer
                X-XSS-Protection "1; mode=block"
                X-Permitted-Cross-Domain-Policies none
                X-Robots-Tag "noindex, nofollow"
                -X-Powered-By
        }

        # Uncomment this block if you use the high speed files backend: https://github.com/nextcloud/notify_push
        handle_path /push/* {
                #reverse_proxy unix//run/notify_push/notify_push.sock
                reverse_proxy 192.168.0.81:7867
        }

        # Uncomment this block if you use onlyoffice: https://github.com/nextcloud/all-in-one/blob/main/Containers/apache/Caddyfile
        #handle_path /onlyoffice/* {
        #       reverse_proxy {$ONLYOFFICE_IP}:{$ONLYOFFICE_PORT} {
        #               header_up X-Forwarded-Host {host}/onlyoffice
        #       }
        #}

        # PicoCMS route
        #route /sites* {
        #       uri strip_prefix /sites
        #       rewrite * /index.php/apps/cms_pico/pico_proxy/{uri}
        #       reverse_proxy 192.168.0.81:8080
        #}

        # PHP block
        # As I said before, i'm a fan of Unix sockets, so I use them as much as I can. But you can do :9000 also for TCP.
        php_fastcgi unix//run/nextcloud/nextcloud.sock {
                # php_fastcgi app:9000 {
                root /var/www/html # This is needed because inside the container the root directory is different from the one I put in the "root" directive of this Caddyfile. If you don't change this, php-fpm will not be able to find the files to process.
                env front_controller_active true # Enable pretty urls
                env modHeadersAvailable true # Avoid sending the security headers twice
                trusted_proxies private_ranges
        }

        # From .htaccess, deny access to sensible files and directories
        @forbidden {
                path /build/* /tests/* /config/* /lib/* /3rdparty/* /templates/* /data/*
                path /.* /autotest* /occ* /issue* /indie* /db_* /console*
                not path /.well-known/*
        }
        error @forbidden 401

        # From .htaccess, set cache for versioned static files (cache-busting)
        @immutable {
                path *.css *.js *.mjs *.svg *.gif *.png *.jpg *.ico *.wasm *.tflite
                query v=*
        }
        header @immutable Cache-Control "max-age=15778463, immutable"

        # From .htaccess, set cache for normal static files
        @static {
                path *.css *.js *.mjs *.svg *.gif *.png *.jpg *.ico *.wasm *.tflite
                not query v=*
        }
        header @static Cache-Control "max-age=15778463"

        # From .htaccess, cache fonts for 1 week
        @woff2 path *.woff2
        header @woff2 Cache-Control "max-age=604800"

        file_server
}

# Uncomment this in addition with the import admin_redir statement allow access to the admin interface only from local networks
(admin_redir) {
        @admin {
                path /admin*
                not remote_ip private_ranges
        }
        redir @admin /
}

5. Links to relevant resources:

So about the Dav configuration, I’ve asked good old ChatGPT how it would handle it, and it came up with the following:

:80 {
  @davclnt {
    header_regexp User-Agent (?i)^DavClnt
  }

  route {
    @davclnt {
      rewrite * /remote.php/webdav/{uri}
    }

    redir @davclnt /302
  }
}

Is this looking correctly? It looks not too bad, but I’m not 100% sure about the {uri} part and if it does the same as $is_args$args.
I really don’t have the knowledge to say if it does the same as the NGINX config and don’t want to blindly enable AI generated configurations.

And about the X-Forwarded-For header, could you maybe explain how this would look like in a Caddyfile?

No, that’s not correct.

Please don’t use ChatGPT, it doesn’t understand Caddy config properly, because it can’t tell the difference between Caddy v1 and Caddy v2, and its dataset is not big enough to be accurate (and as far as I can tell it used incorrect information as part of its dataset).

Please fill out the help topic template. It’s not clear what exactly you’re trying to achieve, and we need more context to be able to help.

Go to the home page and click on New Topic, choose the Help category. Copy the text in the text box(the template) and then make a new reply in this topic with the template and fill it out.

Edit: nevermind, you opened a separate topic, I’ll merge them.

Are you sure you have HTTP/3 enabled on Firefox?

What browsers do is out of scope of Caddy. As you’ve already tested, HTTP/3 does work as you can see with Chrome.

Typically browsers try both HTTP/2 first and only upgrade to HTTP/3 when they see the Alt-Svc header saying that HTTP/3 is supported. Caddy does this, but browsers don’t necessarily need to choose to use HTTP/3. Often, HTTP/2 is still faster in many cases.

Yeah, it should be activated, at least the config value “network.http.http3.enable” is set to true.
But you are right, that is probably not a problem of Caddy then.
Interesting to hear that HTTP/3 is not always faster and thus not always used.

I don’t really think that it was a good idea to merge my post from the other thread, because its two separate questions.
The generated statement was about my question how you can transform this NGINX rule to a Caddyfile equivalent, to complete the switch from NGINX to Caddy:

location = / {
        if ( $http_user_agent ~ ^DavClnt ) {
            return 302 /remote.php/webdav/$is_args$args;
        }
    } 

Can you please explain what part of the proposed configuration is incorrect?

You commented on a wiki post with a question, those are meant to stay evergreen so it’s not the right place for questions.

The problem here is that you defined the same named matcher twice (@davclnt), and you used something that’s a directive and not a matcher inside the 2nd one.

I think you meant to do something like this:

@davclnt header_regexp User-Agent (?i)^DavClnt
redir @devclnt /remote.php/webdav{uri} 302

Okay, I didn’t see that this was a wiki post and didn’t know that you are not supposed to ask there.
But the problem is that now my question regarding the X-Forward-For header …

will probably not reach @jum, so I will just tag him here for some visibility.

And thanks for the explanation about the dav client rule. I will add this to my Caddyfile and check if it works.

Edit: Seems to be working (at least it doesn’t throw any error and Nextcloud also didn’t report anything), but there was a small typo:

# Redirect Dav Clients
@davclnt header_regexp User-Agent (?i)^DavClnt
redir @davclnt /remote.php/webdav{uri} 302

Should I post this to the wiki entry?

Nothing to do, Caddy sets it by default:

Is it actually needed for all NextCloud users, or only in specific cases?

Either way, go ahead and edit the wiki to add that in, I guess.

1 Like

I have found nextcloud to be really icky regarding the X-forwarded-For header. In my case, I am running it via a Unix domain socket which really has no IP to configure for a proxy server. So I finally gave in and pretended to have a proxy at 127.0.0.1:

        php_fastcgi unix//usr/local/var/run/php-fpm.sock {
                header_up X-Forwarded-For "{remote}, 127.0.0.1"
                env front_controller_active true
                env modHeadersAvailable true
        }

I added 127.0.0.1 as a trusted proxy in Nextcloud. If you happen to proxy Nextcloud via an IP socket that this is probably not an issue.

Regarding the DAV header check, I do not see how that is supposed to make a difference. I do not have that and DAV via a linux client worked fine. I did copy the URL from the files settings in the lower left of the nextcloud page when logged in, in my case this looks like this:

https://nextcloud.example.domain/remote.php/dav/files/jum

So the client is already at the proper path, and will stay within that. I do not see why it should be necessary to check for informal client headers.

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