Seafile server version 11.0.12 running on FreeBSD 14 with WebDAV extension enabled. The seafdav.log file records all client requests as coming from 127.0.0.1 (the Apache reverse proxy) rather than the real client IP address. Given that Seafile will be running behind an Apache or Nginx proxy 99% of the time, I can’t imagine any case where this would be at all useful. I have this in the Apache virtual host: <Location /seafdav> ProxyPass http://127.0.0.1:8081/seafdav RequestHeader set X-Forwarded-Proto "https" RequestHeader set X-Forwarded-For %{REMOTE_ADDR}s </Location> Expected behaviour is to look for and log the X-Forwarded-For header, but it does not. The Seafile manual recommends using Fail2Ban to protect the server. This should logically apply to the SeafDAV service also, but it needs the remote IP address. By searching the code I found the routine responsible for logging the client requests is /usr/local/www/haiwen/seafile-server/seahub/thirdpart/wsgidav/wsgidav_app.py and patched it to add xff as shown below. class WsgiDAVApp: ... def __call__(self, environ, start_response): # util.log(“SCRIPT_NAME={!r}, PATH_INFO={!r}”.format( # environ.get(“SCRIPT_NAME”), environ.get(“PATH_INFO”))) path = environ["PATH_INFO"] # --- Added --- xff = environ.get("HTTP_X_FORWARDED_FOR") if xff: environ["REMOTE_ADDR"] = xff.split(",")[0].strip() # --- End --- Now seafdav.log shows the real client IP address, but more needs to be done to properly protect SeafDAV with Fail2Ban. Due to the default behaviour of WebDAV, valid clients will generate “401 Not Authorized” errors in the log before they authenticate. When processed by Fail2Ban, these false positives could block legitimate users. A more reliable indicator for Fail2Ban would be the “Authentication (basic) failed for user” messages, but these do not include the IP address. The file /usr/local/www/haiwen/seafile-server/seahub/thirdpart/wsgidav/http_authenticator.py is responsible for logging authentication failures. The code was patched to read REMOTE_ADDR and append it to the log entries. class HTTPAuthenticator(BaseMiddleware): ... def handle_basic_auth_request(self, environ, start_response): realm = self.domain_controller.get_domain_realm(environ["PATH_INFO"], environ) auth_header = environ["HTTP_AUTHORIZATION"] auth_value = "" try: auth_value = auth_header[len("Basic ") :].strip() except Exception: auth_value = "" auth_value = base64.decodebytes(util.to_bytes(auth_value)) auth_value = util.to_str(auth_value) user_name, password = auth_value.split(":", 1) # --- Get client IP --- client_ip = environ.get("REMOTE_ADDR", "unknown") if self.domain_controller.basic_auth_user(realm, user_name, password, environ): environ["wsgidav.auth.realm"] = realm environ["wsgidav.auth.user_name"] = user_name return self.next_app(environ, start_response) # --- Append client IP --- _logger.warning( "Authentication (basic) failed for user {!r}, realm {!r}, IP {!r}.".format( user_name, realm, client_ip The seafdav.log can now be parsed with a Fail2Ban filter like this: [Definition] failregex = ^.*WARNING\s+:\s+Authentication \([^)]*\) failed for user '[^']*', realm '[^']*', IP '<HOST>'\.$ ignoreregex = I think the SeafDAV code should be fixed to make the logging useful and workable for Fail2Ban, as it is recommended by Seafile.