Demo Environment: This repository contains demo credentials and self-signed certificates for testing only. Do not use in production without proper security configuration.
- Passbolt Pro with OIDC SSO integration (Keycloak over HTTPS)
 - Multi-directory LDAP synchronization (aggregation and direct approaches)
 - LDAPS (implicit TLS) for secure LDAP connections
 - SMTPS for secure email communication
 - Valkey session handling
 - Certificate automation for development and testing
 - SCIM API testing with Bruno
 
- Docker and Docker Compose
 - Passbolt Pro subscription key
 - macOS or Linux
 
- Quick Start
 - LDAP Integration
 - Services Overview
 - Valkey Session Handling
 - Keycloak SSO Configuration
 - SMTP Configuration
 - User and Group Management
 - Testing and Verification
 - Troubleshooting
 
Choose your LDAP integration approach:
Default (Traefik + LDAP Aggregation):
./scripts/setup.shFull stack with Traefik reverse proxy, LDAP aggregation via OpenLDAP meta backend.
LDAP Aggregation with Nginx:
./scripts/setup-aggregation-demo.shOpenLDAP meta backend with Nginx instead of Traefik. Uses direct port access.
Direct Multi-Domain:
./scripts/setup-dual-ldap.shPassbolt connects directly to multiple LDAP servers via PHP configuration.
Single LDAP with Nginx:
./scripts/setup-nginx-single.shBasic single directory setup with Nginx.
All scripts set up LDAP directories, generate GPG keys, and create Passbolt admin user 'ada'. See LDAP Integration for detailed comparison.
- 
Add host entries:
echo "127.0.0.1 passbolt.local keycloak.local smtp.local traefik.local ldap1.local ldap2.local ldap-meta.local" | sudo tee -a /etc/hosts
 - 
Add Passbolt Pro subscription key:
cp /path/to/your/subscription_key.txt ./subscription_key.txt
 - 
Generate TLS certificates:
./scripts/generate-certificates.sh
 - 
Make scripts executable:
chmod +x scripts/ldap/*/*.sh scripts/tests/*/*.sh
 - 
Start the environment:
docker compose up -d
 - 
Setup LDAP test data:
./scripts/ldap/setup/initial-setup.sh
 - 
Create the default admin user (optional - now automatic in setup.sh):
./scripts/ldap/setup/create-admin.sh
 - 
Configure LDAP in Passbolt:
- Log in to Passbolt as administrator
 - Go to Organization Settings > Users Directory
 - Configure LDAP settings (see LDAP Integration)
 
 
Important Notes:
- LDAP users must be set up before creating the Passbolt admin user
 - SMTP: Set "Use TLS" to No (SMTPS implicit TLS is used via 
ssl://smtp.local) - Requires valid Passbolt Pro subscription key in 
subscription_key.txt - Demo credentials are for testing only - use strong credentials in production
 
Two approaches for multi-directory LDAP integration for educational comparison.
| Aspect | Aggregation (Default) | Direct Multi-Domain | 
|---|---|---|
| Architecture | Infrastructure proxy | Application-level | 
| Configuration | Web UI or PHP file | Web UI or PHP file | 
| LDAP Endpoint | Single unified (ldap-meta) | Multiple direct (ldap1, ldap2) | 
| Setup Script | setup-aggregation-demo.sh | 
setup-dual-ldap.sh | 
LDAP1 (Passbolt Inc.) - dc=passbolt,dc=local:
ou=users: Ada, Betty, Carol, Dame, Edith
ou=groups: passbolt, developers, demoteam, admins
LDAP2 (Example Corp) - dc=example,dc=com:
ou=people: John, Sarah, Michael, Lisa
ou=teams: project-teams, security, operations, creative
Aggregation Unified View (dc=unified,dc=local):
dc=passbolt,dc=unified,dc=local → LDAP1
dc=example,dc=unified,dc=local → LDAP2
Passbolt connects to single meta backend. Configure via Web UI or use included PHP configuration (config/passbolt/ldap.php).
LDAP Meta Settings:
- Host: 
ldap-meta.local - Port: 
636(LDAPS) - Base DN: 
dc=unified,dc=local - Username: 
cn=readonly,dc=passbolt,dc=unified,dc=local(PHP config) orcn=admin,dc=unified,dc=local(Web UI) - Password: 
readonly(PHP config) orsecret(Web UI) - Use SSL: 
true 
The meta backend transparently proxies to both LDAP1 and LDAP2.
Passbolt connects directly to both LDAP servers. Configure via Passbolt Web UI or use the included PHP configuration example (config/passbolt/ldap.php).
PHP Configuration Example:
The repository includes a ready-to-use multi-domain configuration at config/passbolt/ldap.php with two domains:
LDAP1 (Passbolt domain):
- Host: 
ldap1.local - Port: 
636(LDAPS) - Base DN: 
dc=passbolt,dc=local - Username: 
cn=readonly,dc=passbolt,dc=local - Password: 
readonly - Paths: 
ou=users,ou=groups 
LDAP2 (Example domain):
- Host: 
ldap2.local - Port: 
636(LDAPS) - Base DN: 
dc=example,dc=com - Username: 
cn=reader,dc=example,dc=com - Password: 
reader123 - Paths: 
ou=people,ou=teams 
Note: The PHP config is not used by default (Web UI configuration takes precedence). To use the PHP config, mount it into the Passbolt container.
Sync Command:
docker compose exec passbolt su -s /bin/bash -c "/usr/share/php/passbolt/bin/cake directory_sync all --persist --quiet" www-dataAll LDAP connections use LDAPS (port 636) with SSL/TLS encryption.
Certificate Management:
- osixia/openldap auto-generates self-signed certificates
 ./scripts/fix-ldaps-certificates.shextracts certificates from containers- Certificates bundled into Passbolt container at build time
 
Test LDAPS:
docker compose exec passbolt openssl s_client -connect ldap:636 \
  -servername ldap.local -CAfile /etc/ssl/certs/ldaps_bundle.crt -briefosixia/openldap Environment Variables:
LDAP_ORGANISATION: "Passbolt"
LDAP_DOMAIN: "passbolt.local"
LDAP_BASE_DN: "dc=passbolt,dc=local"
LDAP_ADMIN_PASSWORD: "P4ssb0lt"
LDAP_TLS: "true"
LDAP_READONLY_USER: "true"
LDAP_READONLY_USER_USERNAME: "readonly"
LDAP_READONLY_USER_PASSWORD: "readonly"Connection Methods:
- LDAPS (implicit TLS): Port 636 - Used by Passbolt
 - STARTTLS: Port 389 - Alternative option
 
Certificate Location in Container:
/container/service/slapd/assets/certs/ldap.crt- Server certificate/container/service/slapd/assets/certs/ca.crt- CA certificate/container/service/slapd/assets/certs/ldap.key- Private key
Passbolt Web UI (Organization Settings > Directory):
- Users Path: 
ou=users - Group Path: 
ou=groups - User Filter: 
(objectClass=inetOrgPerson) - Group Filter: 
(objectClass=groupOfUniqueNames) - Username: 
mail - Email: 
mail - First Name: 
givenName - Last Name: 
sn 
Sync Behavior: One-way read-only from LDAP to Passbolt. LDAP is the source of truth.
- Passbolt LDAP: https://www.passbolt.com/configure/ldap
 - LdapRecord Multi-Domain: https://ldaprecord.com/docs/laravel/v2/configuration
 - OpenLDAP Admin: https://www.openldap.org/doc/admin24/
 
Traefik provides automatic HTTPS routing and service discovery.
./scripts/setup.shUses the default docker-compose.yaml file.
YAML files (fixes indentation issues in Passbolt docs):
config/traefik/traefik.yaml- Main config with HTTP to HTTPS redirectconfig/traefik/conf.d/tls.yaml- TLS 1.2+ settingsconfig/traefik/conf.d/headers.yaml- Security headers
- Passbolt: https://passbolt.local
 - Keycloak: https://keycloak.local
 - SMTP4Dev: https://smtp.local
 - Traefik Dashboard: https://traefik.local
 
./scripts/validate-traefik-config.sh  # Checks YAML syntax, tabs, indentationdocker compose down
docker compose -f docker-compose.nginx.yaml up -d| Service | URL | Credentials | Purpose | 
|---|---|---|---|
| Passbolt | https://passbolt.local | Created during setup | Main application | 
| Keycloak | https://keycloak.local | admin / admin | SSO provider | 
| SMTP4Dev | https://smtp.local | N/A | Email testing | 
| Traefik | https://traefik.local | N/A | Reverse proxy dashboard | 
| LDAP1 | ldap1.local:636 (LDAPS) | cn=readonly,dc=passbolt,dc=local / readonly | Passbolt Inc. directory | 
| LDAP2 | ldap2.local:636 (LDAPS) | cn=reader,dc=example,dc=com / reader123 | Example Corp directory | 
| LDAP Meta | ldap-meta.local:636 (LDAPS) | cn=admin,dc=unified,dc=local / secret | Aggregation proxy | 
| Valkey | valkey:6379 (internal) | N/A | Session storage | 
Valkey provides Redis-compatible session storage for better performance than file-based sessions.
Valkey service:
valkey:
  image: valkey/valkey:9.0-trixie
  ports: ["6379:6379"]
  volumes: [valkey_data:/data]
  command: valkey-server --appendonly yesPassbolt environment variables:
CACHE_CAKECORE_CLASSNAME: Cake\Cache\Engine\RedisEngine
CACHE_CAKECORE_HOST: valkey
CACHE_CAKECORE_PORT: 6379
CACHE_CAKECORE_PASSWORD: ""
CACHE_CAKECORE_DATABASE: 0
SESSION_DEFAULTS: cache# Test connectivity
docker compose exec valkey valkey-cli ping
# Check sessions
docker compose exec valkey valkey-cli keys "*session*"Environment variables documented in official Passbolt documentation:
APP_FULL_BASE_URL- Passbolt application URLDATASOURCES_DEFAULT_*- Database connection settings
EMAIL_TRANSPORT_DEFAULT_*- SMTP server settingsEMAIL_DEFAULT_FROM- Default sender email address
PASSBOLT_PLUGINS_DIRECTORY_SYNC_ENABLED- Enable Directory Sync plugin (Note: Currently not working, see task PB-45139 for fix)PASSBOLT_PLUGINS_SSO_ENABLED- Enable SSO pluginPASSBOLT_PLUGINS_SSO_PROVIDER_OAUTH2_ENABLED- Enable OAuth2 SSO providerPASSBOLT_SECURITY_SSO_SSL_VERIFY- SSO SSL verification
CACHE_CAKECORE_*- Valkey cache engine settingsSESSION_DEFAULTS- Session storage method
- Directory Sync details (host, port, credentials, filters) configured via Passbolt Web UI
 - PHP TLS configuration in 
config/php/ssl.ini 
Stack components:
- Passbolt Pro with OIDC plugin
 - Keycloak 26.4
 - Shared MariaDB (
passboltandkeycloakdatabases) - Shared certificate system
 
Two main configuration files:
- ssl.ini - System-wide PHP SSL certificate paths:
 
[PHP]
openssl.cafile = /etc/ssl/certs/ca-certificates.crt
curl.cainfo = /etc/ssl/certs/ca-certificates.crt- www.conf - PHP-FPM configuration with certificate paths:
 
[www]
php_admin_value[openssl.cafile] = "/etc/ssl/certs/ca-certificates.crt"
php_admin_value[curl.cainfo] = "/etc/ssl/certs/ca-certificates.crt"- 
Access Keycloak Admin Console:
- URL: https://keycloak.local:8443
 - Username: admin
 - Password: admin
 
 - 
Create Realm:
- Click "Create Realm"
 - Name: "passbolt"
 - Click "Create"
 
 - 
Create Client:
- Go to "Clients" > "Create client"
 - Client type: OpenID Connect
 - Client ID: "passbolt-client"
 - Client authentication: ON
 - Authorization: OFF
 - Click "Save"
 
 - 
Configure Client Settings:
- Settings tab:
- Valid redirect URIs: https://passbolt.local/auth/login
 - Web origins: https://passbolt.local
 - Click "Save"
 
 - Credentials tab:
- Copy the generated "Client secret" value
 
 
 - Settings tab:
 - 
Create User:
- Go to "Users" > "Add user"
 - Username: ada
 - Email: [email protected] (must match Passbolt admin email)
 - First name: Ada
 - Last name: Lovelace
 - Click "Create"
 - Go to "Credentials" tab:
- Set password: passbolt
 - Temporary: OFF
 - Click "Set password"
 
 
 
Configure in Passbolt web interface under Administration → Authentication → SSO:
- Issuer URL: 
https://keycloak.local:8443/realms/passbolt - OpenID Configuration Path: 
/.well-known/openid-configuration - Client ID: 
passbolt-client - Client Secret: Use value from Keycloak Credentials tab
 - Scopes: 
openid profile email - SSL Verification: Enabled
 
Full OpenID Configuration URL: https://keycloak.local:8443/realms/passbolt/.well-known/openid-configuration
This endpoint provides the complete OAuth2/OIDC discovery document, including authorization, token, and userinfo endpoints.
Verify Configuration:
curl -k https://keycloak.local:8443/realms/passbolt/.well-known/openid-configuration | jqNote: -k flag skips certificate verification for self-signed certificates in development.
Expected Output (excerpt):
{
  "issuer": "https://keycloak.local:8443/realms/passbolt",
  "authorization_endpoint": "https://keycloak.local:8443/realms/passbolt/protocol/openid-connect/auth",
  "token_endpoint": "https://keycloak.local:8443/realms/passbolt/protocol/openid-connect/token",
  "userinfo_endpoint": "https://keycloak.local:8443/realms/passbolt/protocol/openid-connect/userinfo",
  ...
}- Access Passbolt at https://passbolt.local
 - Click "SSO Login"
 - Redirected to Keycloak
 - Log in with [email protected] / passbolt
 - Redirected back to Passbolt and logged in
 
Note: Configuration examples in assets/ directory.
SMTP4Dev: https://smtp.local (SMTPS port 465)
Passbolt configured to use SMTP4Dev with SMTPS (implicit TLS):
# Email Configuration
EMAIL_TRANSPORT_DEFAULT_HOST: "ssl://smtp.local"
EMAIL_TRANSPORT_DEFAULT_PORT: 25
EMAIL_TRANSPORT_DEFAULT_USERNAME: ""
EMAIL_TRANSPORT_DEFAULT_PASSWORD: ""
EMAIL_DEFAULT_FROM: "[email protected]"
# SMTP Settings (SMTPS - implicit TLS)
# Note: SMTP security settings are configured via Passbolt Web UI# Create certificates directory
mkdir -p smtp4dev/certs
# Generate private key and corresponding certificate
openssl req -x509 -newkey rsa:4096 \
  -keyout smtp4dev/certs/tls.key \
  -out smtp4dev/certs/tls.crt \
  -days 365 -nodes \
  -subj "/CN=smtp.local"
# Create PKCS12 bundle
openssl pkcs12 -export \
  -out smtp4dev/certs/tls.pfx \
  -inkey smtp4dev/certs/tls.key \
  -in smtp4dev/certs/tls.crt \
  -passout pass:changeme- Create a test user in Passbolt
 - Check for registration email in SMTP4Dev web interface
 - Verify email content and headers
 
./scripts/ldap/users/add.sh "Firstname" "Lastname" "[email protected]"
# Example: Add Edith Clarke
./scripts/ldap/users/add.sh "Edith" "Clarke" "[email protected]"./scripts/ldap/add-edith.sh# Check if user was added to LDAP
docker compose exec ldap ldapsearch -x -H ldap://localhost:389 \
  -D "cn=admin,dc=passbolt,dc=local" -w P4ssb0lt \
  -b "dc=passbolt,dc=local" "(cn=username)"
# Run manual sync in Passbolt:
# 1. Log in to Passbolt as an administrator
# 2. Go to Organization Settings > Directory Synchronization
# 3. Click 'Synchronize Now'./scripts/ldap/groups/add.sh <groupname> "<description>" [user1 user2 ...]
# Examples:
./scripts/ldap/groups/add.sh developers "Development Team" ada betty
./scripts/ldap/groups/add.sh developers "Development Team"  # Empty group./scripts/ldap/groups/remove.sh <groupname>./scripts/ldap/groups/add-user.sh <username> <groupname>./scripts/ldap/groups/remove-user.sh <username> <groupname>docker compose exec ldap ldapsearch -x -H ldap://localhost:389 \
  -D "cn=admin,dc=passbolt,dc=local" -w P4ssb0lt \
  -b "dc=passbolt,dc=local" "(cn=<groupname>)"Before testing user removal, ensure Passbolt is configured to suspend users rather than delete them:
- Log in to Passbolt as an administrator
 - Go to Organization Settings > Directory Synchronization
 - Under "Synchronization Options", set:
- "Delete Users" to "No"
 - "Default Group Manager" to your preferred setting
 - "Default Group Admin" to your preferred setting
 
 
# Remove user from LDAP
./scripts/ldap/users/remove.sh <username>
# Run manual sync in Passbolt to suspend the user# Add user back to LDAP
./scripts/ldap/users/add.sh "Firstname" "Lastname" "[email protected]"
# Run manual sync in Passbolt to reactivate the user# Test LDAPS connection from Passbolt container
docker compose exec passbolt openssl s_client -connect ldap:636 \
  -servername ldap.local -CAfile /etc/ssl/certs/ldaps_bundle.crt -brief
# Test LDAPS connection from host (if certificates are trusted)
openssl s_client -connect ldap.local:636 -servername ldap.local -brief# Check SMTP4Dev logs
docker compose logs smtp4dev
# Check Passbolt email logs
docker compose logs passbolt | grep -i email# Test Valkey connectivity
docker compose exec valkey valkey-cli ping
# List active sessions
docker compose exec valkey valkey-cli keys "*session*"./scripts/tests/integration/test-ldap.sh./scripts/tests/sync/test-sync.sh./scripts/tests/scripts/test-scripts.shTest Passbolt's SCIM (System for Cross-domain Identity Management) endpoints using Bruno API client.
- Install Bruno: Download from usebruno.com
 - Open Collection: Open the 
bruno/passbolt-scim-testingfolder in Bruno - Configure Environment: The 
localenvironment is pre-configured with:- Base URL: 
https://passbolt.local - SCIM Base URL: 
https://passbolt.local/scim/v2/935452c2-7a21-4413-8457-8085b50376d3 - Bearer Token: 
pb_wwpDka8H0AORBBVGcL8tU8xtJTJJI0etBO7F0QeshLj - Content-Type: 
application/scim+json 
 - Base URL: 
 
- Get Service Provider Config - Verify SCIM is enabled
 - List Users - See existing users
 - Create User - Add a test user
 - Get User by ID - Verify user creation
 - Update User - Modify user attributes
 - Patch User - Partial update (e.g., deactivate)
 - Search Users - Find users with filters
 - Delete User - Remove test user
 
- Email Type: Passbolt requires 
"type": "work"in email objects - Authentication: Uses Bearer token authentication
 - Content-Type: Must be 
application/scim+json - User Schema: Requires 
userName,name, andemailswith work type 
curl -k --request GET \
  --url 'https://passbolt.local/scim/v2/935452c2-7a21-4413-8457-8085b50376d3/Users?startIndex=1&count=100' \
  --header 'authorization: Bearer pb_wwpDka8H0AORBBVGcL8tU8xtJTJJI0etBO7F0QeshLj' \
  --header 'content-type: application/scim+json'Note: -k flag skips certificate verification for self-signed certificates in development.
- "Email not found" error: Ensure email has 
"type": "work" - Authentication errors: Verify bearer token is correct in environment
 - User not found: Check if user exists in Passbolt first
 
# Check LDAP server status
docker compose exec ldap ldapsearch -x -H ldap://localhost:389 \
  -D "cn=admin,dc=passbolt,dc=local" -w P4ssb0lt \
  -b "dc=passbolt,dc=local" "(objectClass=*)"
# Check Passbolt logs
docker compose logs passbolt
# Check LDAP logs
docker compose logs ldapSymptoms: verify error:num=19:self-signed certificate in certificate chain or "Can't contact LDAP server"
Root Cause: The LDAP server uses its own self-signed certificate issued by docker-light-baseimage, but the certificate bundle is missing the CA certificate or contains incorrect certificates.
Solutions:
- 
Verify the certificate bundle contains both server and CA certificates:
# Check certificate count (should be 2) grep -c "BEGIN CERTIFICATE" certs/ldaps_bundle.crt # Check server certificate subject openssl x509 -in certs/ldaps_bundle.crt -text -noout | grep -A 2 -B 2 "Subject:" | head -3 # Should show: Subject: CN=ldap.local
 - 
If the bundle is incorrect, regenerate it:
# Run the certificate fix script (extracts from container) ./scripts/fix-ldaps-certificates.sh # Rebuild Passbolt container to pick up new bundle docker compose build passbolt docker compose up -d passbolt
 - 
Verify the certificate SAN matches:
openssl x509 -in certs/ldaps_bundle.crt -text -noout | grep -A 5 "Subject Alternative Name" # Should show: DNS:ldap.local
 
# Step 1: Generate new certificate hierarchy
./scripts/generate-certificates.sh
# Step 2: Deploy certificates to LDAP container (optional - for custom certificates)
./scripts/setup-ldap-certs.sh
# Step 3: Fix LDAPS bundle for Passbolt (extracts from container)
./scripts/fix-ldaps-certificates.sh
# Step 4: Rebuild Passbolt container to pick up new certificate bundle
docker compose build passbolt
docker compose up -d passbolt# Verify LDAP certificate details
openssl x509 -in ldap-certs/ldap.crt -text -noout | grep -E "(Subject:|Issuer:|Not Before|Not After)"
# Verify certificate chain integrity
openssl verify -CAfile keys/rootCA.crt ldap-certs/ldap.crt
# Verify LDAPS bundle (used by Passbolt)
openssl x509 -in certs/ldaps_bundle.crt -text -noout | grep -E "(Subject:|Issuer:|Not Before|Not After)"Symptoms: Passbolt cannot connect to LDAP server
Solutions:
- Verify LDAP server is running with TLS enabled
 - Check that custom certificates are properly deployed
 - Ensure network connectivity between containers
 
docker compose exec ldap ldapsearch -x -H ldaps://localhost:636 \
  -D "cn=admin,dc=passbolt,dc=local" -w P4ssb0lt \
  -b "dc=passbolt,dc=local" "(objectClass=*)"Symptoms: Users appear in LDAP but not in Passbolt
Solutions:
- Verify LDAP search filters are correct
 - Check that users have the required 
objectClassattributes - Ensure the bind DN has proper search permissions
 - Run manual synchronization in Passbolt UI
 - Check that users have valid email addresses (required for Passbolt)
 
Note: Remember that sync is one-way from LDAP to Passbolt. Changes to user data must be made in LDAP, not in Passbolt.
Symptoms: "Can't contact LDAP server" during bind attempts
Root Cause: The LDAP admin user cn=admin,dc=passbolt,dc=local may be missing from the LDAP directory.
Solutions:
- 
Check if admin user exists:
docker exec ldap ldapsearch -H ldaps://localhost:636 -D "cn=admin,dc=passbolt,dc=local" -w "P4ssb0lt" -b "dc=passbolt,dc=local" -x "(cn=admin)"
 - 
If admin user is missing, create it:
# Create admin user LDIF file cat > certs/admin_user.ldif << EOF dn: cn=admin,dc=passbolt,dc=local objectClass: simpleSecurityObject objectClass: organizationalRole cn: admin description: LDAP administrator userPassword: P4ssb0lt EOF # Add admin user to LDAP docker cp certs/admin_user.ldif ldap:/tmp/admin_user.ldif docker exec ldap ldapadd -H ldaps://localhost:636 -D "cn=admin,dc=passbolt,dc=local" -w "P4ssb0lt" -f /tmp/admin_user.ldif
 - 
Verify admin user was created:
docker exec ldap ldapsearch -H ldaps://localhost:636 -D "cn=admin,dc=passbolt,dc=local" -w "P4ssb0lt" -b "dc=passbolt,dc=local" -x "(cn=admin)"
 
Symptoms: Group memberships not syncing to Passbolt
Solutions:
- Verify users have activated their Passbolt accounts
 - Check that groups use 
groupOfUniqueNamesobject class - Ensure member references use full DNs
 
Symptoms: Users sync to Passbolt but group memberships fail to sync, or incorrect users appear in groups
Root Cause: The LDAP meta backend's suffixmassage feature doesn't automatically transform DN references in attributes like uniqueMember. Group memberships contain DNs in the original backend format instead of the unified namespace format.
Diagnosis:
# Check group membership DNs through aggregator
docker exec ldap-meta ldapsearch -H ldap://localhost:389 -D "cn=admin,dc=unified,dc=local" -w "secret" -b "dc=unified,dc=local" "(cn=operations)"
# Check user DN in unified namespace
docker exec ldap-meta ldapsearch -H ldap://localhost:389 -D "cn=admin,dc=unified,dc=local" -w "secret" -b "dc=unified,dc=local" "([email protected])"Solution: The setup scripts have been updated to use the correct DN format from the start. If you encounter this issue with existing deployments, update group membership DNs to use the unified namespace format:
# Create LDIF file to fix group memberships
cat > fix_group_memberships.ldif << EOF
# Fix operations group
dn: cn=operations,ou=teams,dc=example,dc=com
changetype: modify
replace: uniqueMember
uniqueMember: cn=User Name,ou=people,dc=example,dc=unified,dc=local
# Fix project-teams group
dn: cn=project-teams,ou=teams,dc=example,dc=com
changetype: modify
replace: uniqueMember
uniqueMember: cn=User Name,ou=people,dc=example,dc=unified,dc=local
EOF
# Apply the fix to backend LDAP server
docker exec -i ldap2 ldapmodify -H ldap://localhost:389 -D "cn=admin,dc=example,dc=com" -w "Ex4mple123" < fix_group_memberships.ldif
# Clean up
rm fix_group_memberships.ldifPrevention: The setup scripts now create groups with the correct DN format automatically. This issue should not occur in fresh deployments.
Verification: After applying the fix, run directory sync in Passbolt to verify group memberships are correctly synchronized.
Symptoms: SSO login doesn't work
Solutions:
- Verify the client ID and secret match between Keycloak and Passbolt
 - Check that the redirect URI is correctly configured
 - Ensure the user exists in both systems with matching email addresses
 
Symptoms: Keycloak fails to connect to the database
Solutions:
- Check database credentials in docker-compose.yaml
 - Verify the keycloak database exists and permissions are set
 - Check MariaDB logs for connection errors
 
Symptoms: Passbolt emails not being sent
Solutions:
- Check certificate files exist and have correct permissions
 - Verify SMTP configuration in Passbolt
 - Check SMTP4Dev logs for connection issues
 
Symptoms: Session handling failures, users getting logged out unexpectedly
Solutions:
- Verify Valkey container is running: 
docker compose ps valkey - Check Valkey connectivity: 
docker compose exec passbolt ping valkey - Check Valkey logs: 
docker compose logs valkey 
Symptoms: "no valid configuration found in file: /traefik.yaml"
Cause: Improperly indented YAML (usually from copying Passbolt docs).
Fix:
# Validate config
./scripts/validate-traefik-config.sh
# Check logs
docker compose logs traefik
# Verify indentation (use 2 spaces, no tabs)
grep -P '\t' config/traefik/*.yaml  # Should return nothingYAML must use consistent spacing:
# Wrong
api:
	dashboard: true
# Correct
api:
  dashboard: trueSymptoms: 404 errors, services not accessible
Fix:
- Check dashboard: http://localhost:8080
 - Verify certificates exist in 
keys/ - Confirm Docker socket mounted: 
/var/run/docker.sock:/var/run/docker.sock:ro - Check service has 
traefik.enable=truelabel 
Send a test email using SMTP4Dev API:
curl -k -X POST https://smtp.local/api/v2/messages \
  -H "Content-Type: application/json" \
  -d '{
    "to": "[email protected]",
    "subject": "Test Email",
    "body": "This is a test email from Passbolt"
  }"Symptoms: Docker mount errors or "permission denied"
Solutions:
chmod 644 certs/*.crt smtp4dev/certs/*.crt
chmod 600 keys/*.key smtp4dev/certs/*.key
chmod 644 smtp4dev/certs/tls.pfxSymptoms: LDAPS connection fails after modifying docker-compose.yaml
Root Cause: Adding incorrect environment variables to the osixia/openldap container can break its internal certificate management.
Solutions:
- 
Use only documented environment variables from the osixia/openldap documentation:
# Basic Configuration LDAP_ORGANISATION: "Passbolt" LDAP_DOMAIN: "passbolt.local" LDAP_BASE_DN: "dc=passbolt,dc=local" LDAP_ADMIN_PASSWORD: "P4ssb0lt" LDAP_CONFIG_PASSWORD: "P4ssb0lt" # TLS Configuration LDAP_TLS: "true" LDAP_TLS_VERIFY_CLIENT: "never" # Readonly User LDAP_READONLY_USER: "true" LDAP_READONLY_USER_USERNAME: "readonly" LDAP_READONLY_USER_PASSWORD: "readonly"
 - 
Avoid custom LDAP_TLS_ variables* that are not in the official documentation
 - 
Restart the LDAP container after changes:
docker compose restart ldap
 
Symptoms: "Can't contact LDAP server" or certificate verification failures
Root Cause: The osixia/openldap image generates its own self-signed certificates using the docker-light-baseimage CA.
Solutions:
- 
Verify the certificate chain:
# Check LDAP server certificate docker compose exec ldap openssl x509 -in /container/service/slapd/assets/certs/ldap.crt -text -noout | grep -A 2 -B 2 "Subject:" # Check CA certificate docker compose exec ldap openssl x509 -in /container/service/slapd/assets/certs/ca.crt -text -noout | grep -A 2 -B 2 "Subject:"
 - 
Verify certificate bundle in Passbolt:
# Check the certificate bundle used by Passbolt openssl x509 -in certs/ldaps_bundle.crt -text -noout | grep -A 2 -B 2 "Subject:" # Should show: CN=ldap.local
 - 
Regenerate certificate bundle if needed:
./scripts/fix-ldaps-certificates.sh docker compose build passbolt docker compose up -d passbolt
 
Symptoms: Passbolt cannot connect to LDAP server
Solutions:
- Verify LDAP server is running with TLS enabled
 - Check that custom certificates are properly deployed
 - Ensure network connectivity between containers
 
docker compose exec ldap ldapsearch -x -H ldaps://localhost:636 \
  -D "cn=admin,dc=passbolt,dc=local" -w P4ssb0lt \
  -b "dc=passbolt,dc=local" "(objectClass=*)"Symptoms: STARTTLS works externally but not from Passbolt container
Root Cause: This is expected behavior. The osixia/openldap image supports both:
- LDAPS (implicit TLS) on port 636 - used by Passbolt
 - LDAP with STARTTLS on port 389 - another option for Passbolt
 
Solutions:
- For Passbolt: Use LDAPS on port 636 (current configuration)
 - For another option: Use LDAP with STARTTLS on port 389
 - Both methods work with the same certificate configuration
 
ls -la certs/ldaps_bundle.crt
ls -la smtp4dev/certs/tls.crt smtp4dev/certs/tls.pfx
ls -la ldap-certs/ldap.crt ldap-certs/ldap.key ldap-certs/ca.crt
ls -la keys/rootCA.crt keys/keycloak.crtls -la subscription_key.txt
# Should show: lrwxr-xr-x ... subscription_key.txt -> /path/to/actual/keydocker compose logs passbolt
docker compose logs -f passboltdocker compose logs ldap
docker compose exec ldap ldapsearch -x -H ldap://localhost:389 \
  -D "cn=admin,dc=passbolt,dc=local" -w P4ssb0lt \
  -b "cn=config" "(objectClass=*)"Key directories:
scripts/- Setup and management scriptscerts/- Certificate files (LDAPS bundle, SMTP certificates)config/- Configuration files (PHP, SSL, database)assets/- Documentation screenshotsdocker-compose.yaml- Main configuration
- Fork the repository
 - Create your feature branch
 - Commit your changes
 - Push to the branch
 - Create a new Pull Request
 
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License (AGPL) as published by the Free Software Foundation version 3.
The name "Passbolt" is a registered trademark of Passbolt SA, and Passbolt SA hereby declines to grant a trademark license to "Passbolt" pursuant to the GNU Affero General Public License version 3 Section 7(e), without a separate agreement with Passbolt SA.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with this program. If not, see GNU Affero General Public License v3.