diff --git a/examples/config-ldap.json b/examples/config-ldap.json
index d7bc0f7f..f649073f 100644
--- a/examples/config-ldap.json
+++ b/examples/config-ldap.json
@@ -19,6 +19,7 @@
           "startTLS": false,
           "baseDN":"ou=Users,dc=example,dc=org",
           "userAttribute": "uid",
+          "userFilter": "(!(nsaccountlock=TRUE))",
           "userGroupAttribute": "memberOf",
           "skipVerify": true,
           "subtreeSearch": true
@@ -69,4 +70,4 @@
       "output": "/tmp/zot.log",
       "audit": "/tmp/zot-audit.log"
     }
-}
\ No newline at end of file
+}
diff --git a/pkg/api/authn.go b/pkg/api/authn.go
index 5558c54d..5f14f6c1 100644
--- a/pkg/api/authn.go
+++ b/pkg/api/authn.go
@@ -272,7 +272,8 @@ func (amw *AuthnMiddleware) tryAuthnHandlers(ctlr *Controller) mux.MiddlewareFun
 			BindDN:             ldapConfig.BindDN(),
 			BindPassword:       ldapConfig.BindPassword(),
 			UserGroupAttribute: ldapConfig.UserGroupAttribute, // from config
-			UserFilter:         fmt.Sprintf("(%s=%%s)", ldapConfig.UserAttribute),
+			UserAttribute:      ldapConfig.UserAttribute,
+			UserFilter:         ldapConfig.UserFilter,
 			InsecureSkipVerify: ldapConfig.SkipVerify,
 			ServerName:         ldapConfig.Address,
 			Log:                ctlr.Log,
diff --git a/pkg/api/config/config.go b/pkg/api/config/config.go
index 07929c21..1fe1b310 100644
--- a/pkg/api/config/config.go
+++ b/pkg/api/config/config.go
@@ -175,6 +175,7 @@ type LDAPConfig struct {
 	UserGroupAttribute string
 	BaseDN             string
 	UserAttribute      string
+	UserFilter         string
 	CACert             string
 }
 
diff --git a/pkg/api/controller_test.go b/pkg/api/controller_test.go
index a61de5fb..4adecc98 100644
--- a/pkg/api/controller_test.go
+++ b/pkg/api/controller_test.go
@@ -90,6 +90,7 @@ var (
 	LDAPBaseDN       = "ou=" + username          //nolint: gochecknoglobals
 	LDAPBindDN       = "cn=reader," + LDAPBaseDN //nolint: gochecknoglobals
 	LDAPBindPassword = "ldappass"                //nolint: gochecknoglobals
+	LDAPUserAttr     = "uid"                     //nolint: gochecknoglobals
 )
 
 func TestNew(t *testing.T) {
@@ -2974,6 +2975,13 @@ func (l *testLDAPServer) Search(boundDN string, req vldap.SearchRequest,
 		}, nil
 	}
 
+	if req.Filter == "(&(uid=locked-user)((!(nsaccountlock=TRUE))))" {
+		return vldap.ServerSearchResult{
+			Entries:    []*vldap.Entry{},
+			ResultCode: vldap.LDAPResultSuccess,
+		}, nil
+	}
+
 	check := fmt.Sprintf("(uid=%s)", username)
 
 	if check == req.Filter {
@@ -3769,13 +3777,14 @@ func TestLDAPClient(t *testing.T) {
 
 		// bad user credentials with anonymous authentication
 		lClient = &api.LDAPClient{
-			Host:         LDAPAddress,
-			Port:         ldapPort,
-			BindDN:       LDAPBindDN,
-			BindPassword: LDAPBindPassword,
-			Base:         LDAPBaseDN,
-			UserFilter:   "(uid=%s)",
-			SkipTLS:      true,
+			Host:          LDAPAddress,
+			Port:          ldapPort,
+			BindDN:        LDAPBindDN,
+			BindPassword:  LDAPBindPassword,
+			Base:          LDAPBaseDN,
+			UserAttribute: LDAPUserAttr,
+			UserFilter:    "",
+			SkipTLS:       true,
 		}
 
 		_, _, _, err = lClient.Authenticate("fail-user-bind", "")
@@ -3783,17 +3792,33 @@ func TestLDAPClient(t *testing.T) {
 
 		// bad user credentials with anonymous authentication
 		lClient = &api.LDAPClient{
-			Host:         LDAPAddress,
-			Port:         ldapPort,
-			BindDN:       LDAPBindDN,
-			BindPassword: LDAPBindPassword,
-			Base:         LDAPBaseDN,
-			UserFilter:   "(uid=%s)",
-			SkipTLS:      true,
+			Host:          LDAPAddress,
+			Port:          ldapPort,
+			BindDN:        LDAPBindDN,
+			BindPassword:  LDAPBindPassword,
+			Base:          LDAPBaseDN,
+			UserAttribute: LDAPUserAttr,
+			UserFilter:    "",
+			SkipTLS:       true,
 		}
 
 		_, _, _, err = lClient.Authenticate("fail-user-bind", "pass")
 		So(err, ShouldNotBeNil)
+
+		// user filtered by additional filter (disabled account in FreeIPA)
+		lClient = &api.LDAPClient{
+			Host:          LDAPAddress,
+			Port:          ldapPort,
+			BindDN:        LDAPBindDN,
+			BindPassword:  LDAPBindPassword,
+			Base:          LDAPBaseDN,
+			UserAttribute: LDAPUserAttr,
+			UserFilter:    "(!(nsaccountlock=TRUE))",
+			SkipTLS:       true,
+		}
+
+		_, _, _, err = lClient.Authenticate("locked-user", "pass")
+		So(err, ShouldNotBeNil)
 	})
 }
 
diff --git a/pkg/api/ldap.go b/pkg/api/ldap.go
index bd9ac45d..ebd9d430 100644
--- a/pkg/api/ldap.go
+++ b/pkg/api/ldap.go
@@ -29,7 +29,8 @@ type LDAPClient struct {
 	UserGroupAttribute string // e.g. "memberOf"
 	Host               string
 	ServerName         string
-	UserFilter         string // e.g. "(uid=%s)"
+	UserFilter         string // e.g. "(!(nsaccountlock=TRUE))"
+	UserAttribute      string // e.g. "uid"
 	Conn               *ldap.Conn
 	ClientCertificates []tls.Certificate // Adding client certificates
 	ClientCAs          *x509.CertPool
@@ -187,7 +188,7 @@ func (lc *LDAPClient) Authenticate(username, password string) (bool, map[string]
 	searchRequest := ldap.NewSearchRequest(
 		lc.Base,
 		searchScope, ldap.NeverDerefAliases, 0, 0, false,
-		fmt.Sprintf(lc.UserFilter, username),
+		lc.userFilter(username),
 		attributes,
 		nil,
 	)
@@ -242,3 +243,13 @@ func (lc *LDAPClient) Authenticate(username, password string) (bool, map[string]
 
 	return true, user, userGroups, nil
 }
+
+func (lc *LDAPClient) userFilter(username string) string {
+	filter := fmt.Sprintf("(%s=%s)", lc.UserAttribute, ldap.EscapeFilter(username))
+
+	if lc.UserFilter != "" {
+		filter = fmt.Sprintf("(&(%s)(%s))", filter, lc.UserFilter)
+	}
+
+	return filter
+}