+
+ +
+

Block desktop client access at the HTTP layer

+

Server administrators can reject Nextcloud Desktop requests at a web server, +reverse proxy, or web application firewall (WAF) by matching the HTTP +User-Agent header.

+

This can be useful as a compatibility guard or an operational policy, for +example to temporarily block all desktop clients or require a particular +client release.

+
+

Warning

+

This is not a security boundary. The client controls the User-Agent +header and can change or omit it. Do not use this rule as the only control +when access must be prevented against a deliberately modified client.

+
+
+

Identify desktop client requests

+

The standard desktop client sends a header with this shape:

+
Mozilla/5.0 (Macintosh) mirall/34.0.2 (Nextcloud, macos-25.5.0 ClientArchitecture: arm64 OsArchitecture: arm64)
+
+
+

The stable marker is mirall/; the value immediately after it is the desktop +client version. The current client constructs this value in +Utility::userAgentString().

+

Use a case-insensitive match for:

+
mirall/
+
+
+

Apply the rule to every request reaching the Nextcloud virtual host. Blocking +only one OCS or WebDAV endpoint does not block desktop access because the +client uses multiple OCS, WebDAV, login-flow, status, and provisioning +endpoints.

+

Nextcloud Server’s +USER_AGENT_CLIENT_DESKTOP +matcher +also recognizes the legacy marker csyncoC/. To include those clients, +replace mirall/ with (mirall|csyncoC)/ in regular-expression rules, or +add a second substring rule where the configuration format does not support +regular expressions.

+
+
+

Choose the policy

+
+

Block every standard desktop client

+

Match the case-insensitive substring:

+
mirall/
+
+
+
+
+

Block releases older than 34.0.2

+

Web-server regular expressions do not compare arbitrary version strings as +semantic versions. The following case-insensitive expression is deliberately +written for one minimum release, 34.0.2:

+
mirall/(([0-9]|[12][0-9]|3[0-3])[.]|34[.]0[.](0|1)([^0-9]|$))
+
+
+

It has these results:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Desktop version

Result

3.14.3

blocked

33.0.0

blocked

34.0.0

blocked

34.0.1

blocked

34.0.2

allowed by this rule

34.1.0

allowed by this rule

35.0.0

allowed by this rule

+

The expression assumes normal release versions with three numeric components. +Do not change only the version text in the expression. Build and test a new +set of numeric ranges whenever the minimum release changes. A WAF that can +extract the version and compare three integer fields is preferable when the +minimum changes frequently.

+

Return 403 Forbidden. Do not return 401 Unauthorized: by HTTP +semantics, that status asks the client to authenticate and obscures that this +is an administrative policy. The desktop client is not guaranteed to display +a custom response body, so the administrator should communicate the policy +separately to users.

+

The examples below assume that Nextcloud has its own virtual host at +cloud.example.com. If the host serves other applications, additionally +scope the rule to the Nextcloud URL prefix.

+
+
+
+

nginx

+

Define one map in the http context. Enable exactly one of the two match +entries:

+
http {
+    map $http_user_agent $block_nextcloud_desktop {
+        default 0;
+
+        # Policy A: block every standard desktop client.
+        ~*mirall/ 1;
+
+        # Policy B: instead block releases older than 34.0.2.
+        # ~*mirall/(([0-9]|[12][0-9]|3[0-3])[.]|34[.]0[.](0|1)([^0-9]|$)) 1;
+    }
+
+    server {
+        server_name cloud.example.com;
+
+        if ($block_nextcloud_desktop) {
+            return 403 "Nextcloud desktop client access is disabled.\n";
+        }
+
+        # The existing Nextcloud configuration follows here.
+    }
+}
+
+
+

The map directive is valid only in the http context. The resulting +variable is then checked in the Nextcloud server block. A conditional +containing only return is sufficient here; no URI rewrite is involved.

+

Validate and reload the configuration with the commands appropriate to the +installation, commonly:

+
nginx -t
+systemctl reload nginx
+
+
+

See the nginx documentation for +map and +return.

+
+
+

Apache HTTP Server 2.4

+

Place SetEnvIfNoCase in the Nextcloud virtual host and deny requests +carrying the resulting environment variable. Enable exactly one +SetEnvIfNoCase line:

+
<VirtualHost *:443>
+    ServerName cloud.example.com
+
+    # Policy A: block every standard desktop client.
+    SetEnvIfNoCase User-Agent "mirall/" block_nextcloud_desktop
+
+    # Policy B: instead block releases older than 34.0.2.
+    # SetEnvIfNoCase User-Agent "mirall/(([0-9]|[12][0-9]|3[0-3])[.]|34[.]0[.](0|1)([^0-9]|$))" block_nextcloud_desktop
+
+    <Location "/">
+        <RequireAll>
+            Require all granted
+            Require not env block_nextcloud_desktop
+        </RequireAll>
+    </Location>
+
+    # The existing Nextcloud configuration follows here.
+</VirtualHost>
+
+
+

For a subdirectory installation, such as +https://cloud.example.com/nextcloud, use <Location "/nextcloud"> +instead. The modules mod_setenvif and mod_authz_core must be enabled.

+

Validate and reload the configuration with the commands appropriate to the +installation, commonly:

+
apachectl configtest
+systemctl reload apache2
+
+
+

The Apache documentation describes this +SetEnvIfNoCase and Require blocking pattern +and also notes that a client can circumvent a User-Agent rule.

+
+
+

Caddy 2

+

Place the matcher and response before the existing Nextcloud handler. Enable +exactly one matcher line:

+
cloud.example.com {
+    # Policy A: block every standard desktop client.
+    @blockedNextcloudDesktop header_regexp User-Agent (?i)mirall/
+
+    # Policy B: instead block releases older than 34.0.2.
+    # @blockedNextcloudDesktop header_regexp User-Agent (?i)mirall/(([0-9]|[12][0-9]|3[0-3])[.]|34[.]0[.](0|1)([^0-9]|$))
+
+    respond @blockedNextcloudDesktop "Nextcloud desktop client access is disabled." 403
+
+    # The existing Nextcloud file_server, php_fastcgi, or reverse_proxy
+    # directives follow here.
+}
+
+
+

Caddy uses RE2 regular expressions. The example therefore uses capturing +groups rather than PCRE-only constructs.

+

Validate and reload the configuration with the commands appropriate to the +installation, commonly:

+
caddy validate --config /etc/caddy/Caddyfile
+systemctl reload caddy
+
+
+

See the Caddy documentation for +header_regexp +and respond.

+
+
+

HAProxy

+

Add the ACL and deny action to the HTTP frontend that receives Nextcloud +traffic. Enable exactly one blocked_nextcloud_desktop ACL:

+
frontend https_frontend
+    bind :443 ssl crt /etc/haproxy/certs/cloud.example.com.pem
+
+    acl nextcloud_host hdr(host) -i cloud.example.com cloud.example.com:443
+
+    # Policy A: block every standard desktop client.
+    acl blocked_nextcloud_desktop req.hdr(User-Agent) -m sub -i mirall/
+
+    # Policy B: instead block releases older than 34.0.2.
+    # acl blocked_nextcloud_desktop req.hdr(User-Agent) -m reg -i mirall/(([0-9]|[12][0-9]|3[0-3])[.]|34[.]0[.](0|1)([^0-9]|$))
+
+    http-request deny deny_status 403 if nextcloud_host blocked_nextcloud_desktop
+
+    # The existing backend selection follows here.
+
+
+

Keep the host ACL when this frontend serves more than Nextcloud. Validate and +reload the configuration with the commands appropriate to the installation, +commonly:

+
haproxy -c -f /etc/haproxy/haproxy.cfg
+systemctl reload haproxy
+
+
+

See the HAProxy documentation for +ACLs +and +http-request deny.

+
+
+

Verify the rule

+

Test from a system that reaches the same proxy or WAF path as real users. The +following probes do not require Nextcloud credentials:

+
# Must be 403 for both policies.
+curl -sS -o /dev/null -w '%{http_code}\n' \
+    -A 'Mozilla/5.0 (Linux) mirall/34.0.1 (Nextcloud, linux)' \
+    https://cloud.example.com/status.php
+
+# Must not be blocked by the minimum-version policy. It is still 403 when
+# the block-all policy is active.
+curl -sS -o /dev/null -w '%{http_code}\n' \
+    -A 'Mozilla/5.0 (Linux) mirall/34.0.2 (Nextcloud, linux)' \
+    https://cloud.example.com/status.php
+
+# Must not be blocked by either desktop rule.
+curl -sS -o /dev/null -w '%{http_code}\n' \
+    -A 'Mozilla/5.0 (compatible; policy-check)' \
+    https://cloud.example.com/status.php
+
+
+

Also verify these operational details:

+
    +
  1. The rule covers all Nextcloud paths and HTTP methods, including WebDAV +methods such as PROPFIND, PUT, and DELETE.

  2. +
  3. The origin server cannot be reached through another hostname or IP address +that bypasses the proxy or WAF.

  4. +
  5. Proxy layers preserve the original User-Agent header until the component +enforcing the rule has evaluated it.

  6. +
  7. Access and WAF logs record blocked requests, the matched policy, and the +returned status.

  8. +
  9. A rollback consists of disabling the match rule and reloading the +configuration; it does not require changing Nextcloud itself.

  10. +
+
+
+ + +
+