Installation via Docker
Beschreibung:
Ein ldap Server für Adressbücher mit carddav sync.
Installation:
Docker installieren
apt install docker.io docker-compose curl
Nun Projektverzeichnisse erstellen
ldap-carddav-stack/
├── docker-compose.yml
├── .env
├── ldap-carddav/
│ └── conf.php # deine Konfiguration
├── Dockerfile # für ldap-carddav
├── ldap-carddav-data
mkdir -p /root/ldap-carddav-stack/ldap-carddav
mkdir -p /root/ldap-carddav-stack/ldap-carddav-data
Dockerfile erstellen zum image bauen
nano /root/ldap-carddav-stack/Dockerfile
Inhalt
FROM debian:bullseye
ENV DEBIAN_FRONTEND=noninteractive
# Abhängigkeiten installieren
RUN apt-get update && apt-get install -y \
apache2 \
php \
php-ldap \
php-xml \
php-mbstring \
php-sqlite3 \
sqlite3 \
libapache2-mod-php \
nano \
curl \
ldap-utils \
composer \
&& apt-get clean
# ldap-carddav klonen
RUN git clone https://github.com/isubsoft/ldap-carddav.git /var/www/html/ldap-carddav
# Composer-Abhängigkeiten installieren
WORKDIR /var/www/html/ldap-carddav
RUN composer install
# Rewrite-Modul aktivieren
RUN a2enmod rewrite
# 000-default.conf ersetzen
RUN rm /etc/apache2/sites-enabled/000-default.conf && \
echo '<VirtualHost *:80>\n\
ServerAdmin admin@example.org\n\
DocumentRoot /var/www/html/ldap-carddav\n\
\n\
<Directory /var/www/html/ldap-carddav>\n\
Options Indexes FollowSymLinks\n\
AllowOverride All\n\
Require all granted\n\
DirectoryIndex server.php\n\
RewriteEngine On\n\
RewriteCond %{REQUEST_FILENAME} !-f\n\
RewriteCond %{REQUEST_FILENAME} !-d\n\
RewriteRule ^(.*)$ server.php [QSA,L]\n\
</Directory>\n\
\n\
Redirect 301 /.well-known/carddav /server.php\n\
</VirtualHost>' > /etc/apache2/sites-enabled/000-default.conf
# Apache starten
CMD ["apachectl", "-D", "FOREGROUND"]
EXPOSE 80
Die .env Datei
nano /root/ldap-carddav-stack/.env
Inhalt
LDAP_ORGANISATION=ExampleCorp
LDAP_DOMAIN=example
LDAP_TOP_DOMAIN=local
LDAP_ADMIN_PASSWORD=admin
Die compose Datei
nano /root/ldap-carddav-stack/docker-compose.yml
Inhalt
version: '3.8'
services:
ldap:
image: osixia/openldap:1.5.0
container_name: ldap
environment:
LDAP_ORGANISATION: ${LDAP_ORGANISATION}
LDAP_DOMAIN: ${LDAP_DOMAIN}.${LDAP_TOP_DOMAIN}
LDAP_ADMIN_PASSWORD: ${LDAP_ADMIN_PASSWORD}
volumes:
- ./ldap_data:/var/lib/ldap
- ./ldap_config:/etc/ldap/slapd.d
ports:
- "389:389"
phpldapadmin:
image: osixia/phpldapadmin:0.9.0
container_name: phpldapadmin
environment:
PHPLDAPADMIN_LDAP_HOSTS: ldap
ports:
- "6443:443"
carddav:
build:
context: .
dockerfile: Dockerfile
container_name: ldap-carddav
ports:
- "80:80"
volumes:
- ./ldap-carddav/conf.php:/var/www/html/ldap-carddav/conf/conf.php:ro
- ./ldap-carddav-data:/var/www/html/ldap-carddav/data
depends_on:
- ldap
environment:
- LDAP_HOST=ldap
- LDAP_BASE_DN=dc=${LDAP_DOMAIN},dc=${LDAP_TOP_DOMAIN}
- LDAP_BIND_DN=cn=admin,dc=${LDAP_DOMAIN},dc=${LDAP_TOP_DOMAIN}
- LDAP_BIND_PASSWORD=${LDAP_ADMIN_PASSWORD}
# - LDAP_BASE_DN_SYNC=dc=${LDAP_DOMAIN},dc=${LDAP_TOP_DOMAIN}
# - LDAP_BIND_DN_SYNC=cn=shacker,dc=${LDAP_DOMAIN},dc=${LDAP_TOP_DOMAIN}
# - LDAP_BIND_PASSWORD_SYNC=1234
Container starten:
docker-compose up -d
PHP File
nano /root/ldap-carddav-stack/ldap-carddav/conf.php
Version 1: Nur ein Adressbuch, für alle schreibend:
Inhalt
<?php
$config = [];
// === TEMP / DATA ===
$config['tmpdir'] = '%systempdir';
$config['datadir'] = '/var/www/html/ldap-carddav/data';
// === DATABASE ===
$config['sync_database'] = [
'dsn' => 'sqlite:/var/www/html/ldap-carddav/data/cards.db',
'username' => '',
'password' => '',
'options' => [],
'init_commands' => []
];
// === LDAP SERVER CONFIG ===
$config['server']['ldap'] = [
'host' => getenv('LDAP_HOST') ?: 'localhost',
'network_timeout' => 10,
'connection_security' => 'none'
];
// === LDAP AUTH ===
$config['auth']['ldap'] = [
'base_dn' => getenv('LDAP_BASE_DN') ?: 'dc=example,dc=local',
'bind_dn' => '%dn',
'bind_pass' => '%p',
'search_base_dn' => '',
'search_filter' => '(&(objectclass=inetOrgPerson)(uid=%u))',
'search_bind_dn' => getenv('LDAP_BIND_DN') ?: 'cn=admin,dc=example,dc=local',
'search_bind_pw' => getenv('LDAP_BIND_PASSWORD') ?: 'admin',
'scope' => 'list'
];
// === PRINCIPAL SEARCH ===
$config['principal']['ldap'] = [
'base_dn' => getenv('LDAP_BASE_DN') ?: 'dc=example,dc=local',
'search_base_dn' => '',
'search_filter' => '(&(objectclass=inetOrgPerson)(uid=*))',
'search_bind_dn' => getenv('LDAP_BIND_DN') ?: 'cn=admin,dc=example,dc=local',
'search_bind_pw' => getenv('LDAP_BIND_PASSWORD') ?: 'admin',
'scope' => 'list',
'fieldmap' => [
'id' => 'uid',
'displayname' => 'cn',
'mail' => 'mail'
]
];
// Hinweis: Die folgenden Einträge sind stark gekürzt. Siehe Original für volle Struktur.
// Du kannst z. B. $config['card']['addressbook']['ldap']['me'], ['global'], ['personal'] wie oben mit getenv() einbinden.
// Beispiel für ein Adressbuch-Eintrag mit bind_dn über ENV
$config['card']['addressbook']['ldap']['personal'] = [
'name' => 'starface',
'description' => 'Starface Kontakte',
'user_specific' => true,
'writable' => true,
'group_LDAP_Object_Classes' => ['groupOfNames'],
'group_required_fields' => ['cn', 'member'],
'group_LDAP_rdn' => 'cn',
'group_member_map' => [ 'MEMBER' => [ 'field_name' => 'member' ] ],
'base_dn' => getenv('LDAP_BASE_DN') ?: 'dc=example,dc=local',
'filter' => '(objectClass=inetOrgPerson)',
'bind_dn' => getenv('LDAP_BIND_DN') ?: 'cn=admin,dc=example,dc=local',
'bind_pass' => getenv('LDAP_BIND_PASSWORD') ?: 'admin',
'scope' => 'sub',
'LDAP_Object_Classes' => ['inetOrgPerson'],
'required_fields' => ['cn','sn'],
'LDAP_rdn' => 'cn',
// Schreibrechte aktivieren
//'field_acl' => [
// 'eval' => 'w'
// 'list' => ['displayName', 'homePhone', 'telephoneNumber', 'facsimileTelephoneNumber', 'pager', 'mobile', 'homePostalAddress', 'preferredLanguage']
// leere Liste = alles erlaubt
// ],
// Vollständige Feldzuordnung für Outlook / CalDAV
'fieldmap' => [
'FN' => ['field_name' => 'cn'],
'N' => ['field_name' => [
'last_name' => 'sn',
'first_name' => 'givenName',
'prefix' => 'personalTitle'
]],
'EMAIL' => ['field_name' => 'mail'],
'ORG' => ['field_name' => [
'org_name' => 'o',
'org_unit_name' => 'ou'
]],
'TITLE' => ['field_name' => 'title'],
'ROLE' => ['field_name' => 'employeeType'],
'NICKNAME' => ['field_name' => 'displayName'],
'PHOTO' => [[
'field_name' => 'jpegphoto',
'parameters' => [],
'reverse_map_parameter_index' => 0,
'decode_file' => true
]],
'NOTE' => ['field_name' => 'description'],
'TEL' => [
[ // Fax number
'field_name' => 'facsimileTelephoneNumber',
'parameters' => [
['TYPE' => ['fax']],
['TYPE' => ['fax', 'work']],
['TYPE' => ['work', 'fax']],
['TYPE' => 'facsimile'],
['TYPE' => ['voice', 'fax']],
['TYPE' => ['fax', 'voice']],
null
],
'reverse_map_parameter_index' => 0
],
[ // Work number
'field_name' => 'telephoneNumber',
'parameters' => [
['TYPE' => ['work']],
['TYPE' => ['voice', 'work']],
['TYPE' => 'work'],
['TYPE' => 'voice'],
null
],
'reverse_map_parameter_index' => 0
],
[ // Home number
'field_name' => 'homePhone',
'parameters' => [
['TYPE' => ['home']],
['TYPE' => ['voice', 'home']],
['TYPE' => 'home'],
null
],
'reverse_map_parameter_index' => 0
],
[ // Mobile number
'field_name' => 'mobile',
'parameters' => [
['TYPE' => ['cell']],
['TYPE' => ['voice', 'cell']],
['TYPE' => 'cell'],
null
],
'reverse_map_parameter_index' => 0
],
[ // Pager
'field_name' => 'pager',
'parameters' => [
['TYPE' => ['pager']],
null
],
'reverse_map_parameter_index' => 0
]
],
'NOTE' => [
'field_name' => 'description',
'parameters' => [],
'reverse_map_parameter_index' => 0
],
'ADR' => [
[
'field_name' => [
'po_box' => 'postOfficeBox',
'street' => 'street',
'locality' => 'l',
'province' => 'st',
'postal_code' => 'postalCode'
],
'parameters' => ['TYPE' => 'work'],
'map_component_separator' => ';',
'reverse_map_parameter_index' => 0
],
// Privatadresse
[
'field_name' => 'homePostalAddress',
'parameters' => ['TYPE' => 'home'],
'map_component_separator' => '$',
'reverse_map_parameter_index' => 0
]
],
'LANG' => ['field_name' => 'preferredLanguage']
]
];
Version 2, zwei Adressbücher aber eins nur schreibend mit einer Gruppe
function getWritableUserIdsFromLdapGroup($group_dn) {<?php
$ldapconfig = ldap_connect([];
// === TEMP / DATA ===
$config['tmpdir'] = '%systempdir';
$config['datadir'] = '/var/www/html/ldap-carddav/data';
// === DATABASE ===
$config['sync_database'] = [
'dsn' => 'sqlite:/var/www/html/ldap-carddav/data/cards.db',
'username' => '',
'password' => '',
'options' => [],
'init_commands' => []
];
// === LDAP SERVER CONFIG ===
$config['server']['ldap'] = [
'host' => getenv('LDAP_HOST') ?: 'localhost'),
'network_timeout' => 10,
'connection_security' => 'none'
];
ldap_set_option($ldap,// LDAP_OPT_PROTOCOL_VERSION,=== 3);LDAP ldap_bind(AUTH ===
$ldap,config['auth']['ldap'] = [
'base_dn' => getenv('LDAP_BASE_DN') ?: 'dc=example,dc=local',
'bind_dn' => '%dn',
'bind_pass' => '%p',
'search_base_dn' => '',
'search_filter' => '(&(objectclass=inetOrgPerson)(uid=%u))',
'search_bind_dn' => getenv('LDAP_BIND_DN') ?: 'cn=admin,dc=example,dc=local',
'search_bind_pw' => getenv('LDAP_BIND_PASSWORD') ?: 'admin',
'scope' => 'list'
];
// === PRINCIPAL SEARCH ===
$config['principal']['ldap'] = [
'base_dn' => getenv('LDAP_BASE_DN') ?: 'dc=example,dc=local',
'search_base_dn' => '',
'search_filter' => '(&(objectclass=inetOrgPerson)(uid=*))',
'search_bind_dn' => getenv('LDAP_BIND_DN') ?: 'cn=admin,dc=example,dc=local',
'search_bind_pw' => getenv('LDAP_BIND_PASSWORD') ?: 'admin',
'scope' => 'list',
'fieldmap' => [
'id' => 'uid',
'displayname' => 'cn',
'mail' => 'mail'
]
];
// Funktion einfügen oder inkludieren
function getWritableUserIdsFromLdapGroup(): array {
$groupCN = getenv('LDAP_WRITE_GROUP') ?: 'write';
$ldapDomain = getenv('LDAP_DOMAIN') ?: 'example';
$ldapTopDomain = getenv('LDAP_TOP_DOMAIN') ?: 'local';
$groupDN = "cn={$groupCN},dc={$ldapDomain},dc={$ldapTopDomain}";
$ldap = ldap_connect('localhost');
ldap_set_option($resultldap, LDAP_OPT_PROTOCOL_VERSION, 3);
$bindDN = getenv('LDAP_BIND_DN') ?: "cn=admin,dc={$ldapDomain},dc={$ldapTopDomain}";
$bindPW = getenv('LDAP_BIND_PASSWORD') ?: 'admin';
if (!@ldap_bind($ldap, $bindDN, $bindPW)) {
error_log("⚠️ LDAP Bind fehlgeschlagen mit DN: $bindDN");
return [];
}
$results = ldap_search($ldap, $group_dn,groupDN, '(objectClass=groupOfNames)', ['member']);
if (!$results) {
error_log("⚠️ Konnte Mitglieder von Gruppe $groupDN nicht abrufen.");
return [];
}
$entries = ldap_get_entries($ldap, $result)results);
if (!isset($entries[0]['member'])) {
return [];
}
$uids = [];
if ($entries['count'] > 0) {
foreach ($entries[0]['member'] as $key => $dn) {
if (is_numeric($dnkey) ===&& 'count') continue;
// Jetzt DN nach UID parsen
if (preg_match('/^uid=([^,]+)/i', $dn, $match)matches)) {
$uids[] = $match[matches[1];
}
}
}
ldap_unbind($ldap);
return $uids;
}
// Benutzer-ID abrufen
$user = $_SERVER['PHP_AUTH_USER'] ?? null;
// Schreibrechte prüfen
$writeUsers = getWritableUserIdsFromLdapGroup();
$isWritable = in_array($user, $writeUsers);
// Hinweis: Die folgenden Einträge sind stark gekürzt. Siehe Original für volle Struktur.
// Du kannst z. B. $config['cn=carddav-write,ou=Groups,card']['addressbook']['ldap']['me'], ['global'], ['personal'] wie oben mit getenv() einbinden.
// Beispiel für ein Adressbuch-Eintrag mit bind_dn über ENV
$config['card']['addressbook']['ldap']['personal'] = [
'name' => 'starface',
'description' => 'Starface Kontakte',
'user_specific' => true,
'writable' => $isWritable,
'group_LDAP_Object_Classes' => ['groupOfNames'],
'group_required_fields' => ['cn', 'member'],
'group_LDAP_rdn' => 'cn',
'group_member_map' => [ 'MEMBER' => [ 'field_name' => 'member' ] ],
'base_dn' => getenv('LDAP_BASE_DN') ?: 'dc=example,dc=local',
'filter' => '(objectClass=inetOrgPerson)',
'bind_dn' => getenv('LDAP_BIND_DN') ?: 'cn=admin,dc=example,dc=local',
'bind_pass' => getenv('LDAP_BIND_PASSWORD') ?: 'admin',
'scope' => 'sub',
'LDAP_Object_Classes' => ['inetOrgPerson'],
'required_fields' => ['cn','sn'],
'LDAP_rdn' => 'cn',
// Schreibrechte aktivieren
//'field_acl' => [
// 'eval' => 'w'
// 'list' => ['displayName', 'homePhone', 'telephoneNumber', 'facsimileTelephoneNumber', 'pager', 'mobile', 'homePostalAddress', 'preferredLanguage']
// leere Liste = alles erlaubt
// ],
// Vollständige Feldzuordnung für Outlook / CalDAV
'fieldmap' => [
'FN' => ['field_name' => 'cn'],
'N' => ['field_name' => [
'last_name' => 'sn',
'first_name' => 'givenName',
'prefix' => 'personalTitle'
]],
'EMAIL' => ['field_name' => 'mail'],
'ORG' => ['field_name' => [
'org_name' => 'o',
'org_unit_name' => 'ou'
]],
'TITLE' => ['field_name' => 'title'],
'ROLE' => ['field_name' => 'employeeType'],
'NICKNAME' => ['field_name' => 'displayName'],
'PHOTO' => [[
'field_name' => 'jpegphoto',
'parameters' => [],
'reverse_map_parameter_index' => 0,
'decode_file' => true
]],
'NOTE' => ['field_name' => 'description'],
'TEL' => [
[ // Fax number
'field_name' => 'facsimileTelephoneNumber',
'parameters' => [
['TYPE' => ['fax']],
['TYPE' => ['fax', 'work']],
['TYPE' => ['work', 'fax']],
['TYPE' => 'facsimile'],
['TYPE' => ['voice', 'fax']],
['TYPE' => ['fax', 'voice']],
null
],
'reverse_map_parameter_index' => 0
],
[ // Work number
'field_name' => 'telephoneNumber',
'parameters' => [
['TYPE' => ['work']],
['TYPE' => ['voice', 'work']],
['TYPE' => 'work'],
['TYPE' => 'voice'],
null
],
'reverse_map_parameter_index' => 0
],
[ // Home number
'field_name' => 'homePhone',
'parameters' => [
['TYPE' => ['home']],
['TYPE' => ['voice', 'home']],
['TYPE' => 'home'],
null
],
'reverse_map_parameter_index' => 0
],
[ // Mobile number
'field_name' => 'mobile',
'parameters' => [
['TYPE' => ['cell']],
['TYPE' => ['voice', 'cell']],
['TYPE' => 'cell'],
null
],
'reverse_map_parameter_index' => 0
],
[ // Pager
'field_name' => 'pager',
'parameters' => [
['TYPE' => ['pager']],
null
],
'reverse_map_parameter_index' => 0
]
],
'NOTE' => [
'field_name' => 'description',
'parameters' => [],
'reverse_map_parameter_index' => 0
],
'ADR' => [
[
'field_name' => [
'po_box' => 'postOfficeBox',
'street' => 'street',
'locality' => 'l',
'province' => 'st',
'postal_code' => 'postalCode'
],
'parameters' => ['TYPE' => 'work'],
'map_component_separator' => ';',
'reverse_map_parameter_index' => 0
],
// Privatadresse
[
'field_name' => 'homePostalAddress',
'parameters' => ['TYPE' => 'home'],
'map_component_separator' => '$',
'reverse_map_parameter_index' => 0
]
],
'LANG' => ['field_name' => 'preferredLanguage']
]
];
Nun noch die. env erweitern. write ist hier der gruppenname
...
LDAP_WRITE_GROUP=write
...
Aufrufen LDAP und co:
-
phpLDAPadmin:
https://localhost:6443
Benutzername aus unserem Beispiel : cn=admin,dc=example,dc=local
Passwort aus unserem Beispiel : admin -
ldap-carddav WebDAV/CardDAV: http://localhost/ldap-carddav/
Die Benutzer dazu werden im LDAP Webgui angelegt, dazu ein ein eigenes Kaptitel
Datenbank initialiseren
docker-compose exec carddav /bin/bash
sqlite3 /var/www/html/ldap-carddav/data/cards.db < /var/www/html/ldap-carddav//sql/sqlite/ddl.sql
php /var/www/html/ldap-carddav/src/App/syncdb.php init
Danach vom conatiner wieder abmelden und chmod 777 über die card.db
chown www-data:www-data -R /var/www/html/ldap-carddav/data
PHP ini Änderungen durchführen und neu mit der Datenbank synchroniesieren:
Wenn die PHP geändert wird um zum Beispiel Felder hinzugefügt werden, muss ide Datenbank neu initialisert werden.
docker-compose exec carddav /bin/bash
php /var/www/html/ldap-carddav/src/App/syncdb.php
Ausgabe:
Dort 0 Auswählen
hoose the entity you want to operate upon. Enter 0 for addressbook and 1 for user:
Nun mit 3 bestätigen
Enter the operation to perform on address book. Enter 0 to list, 1 to add, 2 to rename and 3 to delete:
Nun Adressbuchname eingeben:personal.
Enter name of the address book to delete:
Nun wurde das Buch gelöscht
Address book 'personal' has been deleted.
Nun kann ein neuer init stattfinden
php /var/www/html/ldap-carddav/src/App/syncdb.php init
Ausgabe:
Initializing sync database ...
Address book 'personal' has been successfully added to sync database.
Address book(s) successfully imported.
Benutzer anlegen:
Im LDAP Webgui einloggen unter
https://<ip>:6443
Dort neues child Element anlegen...
Vom Typ PosixGroup
mit dem namen users
Nun nochmals bestätigen
nun eine weitere elemt vom typ organizationRole mit den namen write anlegen
nun den Namen vergeben
runter scrollen bis create object
Nun einen Benutzer anlegen vom typ inetOrgPerson
Daten ausfüllen
Nun sieht das ganz so aus:
Nun den benutzer im carddav Backend anlegen, nicht im ldap, das haben wir gerade getan.
docker-compose exec carddav /bin/bash
php /var/www/html/ldap-carddav/src/App/syncdb.php
Fehlersuche:
Invalid Credentials:
Sollte Verbindung nicht zustande kommen, dann mal in die Apache2 log schauen.
Im container anmelden
docker-compose exec carddav /bin/bash
cat /var/log/apache2/error.log
Ausgabe:
Testen der Daten
ldapwhoami -x -D "cn=admin,dc=example,dc=local" -w admin -H ldap://ldap
Ausgabe:
Testen ob ein Objekt sich ändern lässt
ldapmodify -x -D "cn=admin,dc=example,dc=local" -w admin -H ldap://ldap
Einfügen und dann strg+d drücken
dn: cn=shacker,dc=example,dc=local
changetype: modify
replace: mail
mail: test@example.org
Ein komplettes Objekt ausgeben
ldapsearch -x \
-D "cn=admin,dc=example,dc=local" \
-w admin \
-b "cn=Wolf\, SDaniel,dc=example,dc=local" \
-LLL
Ausgabe:
Database readonly:
Fehler aus der /var/log/apache2/error.log
attempt to write a readonly database, referer: http://192.168.0.231/server.php/addressbooks/mprangen/
[Thu Jul 31 19:11:24.152676 2025] [php7:notice] [pid 9:tid 9] [client 192.168.0.26:51450] Database query could not be executed: ISubsoft\\DAV\\CardDAV\\Backend\\LDAP::fullSyncOperation at line no 1856, SQLSTATE[HY000]: General error: 8 attempt to write a readonly database, referer: http://192.168.0.231/server.php/addressbooks/mprangen/
[Thu Jul 31 19:12:16.335653 2025] [php7:notice] [pid 12:tid 12] [client 192.168.0.26:51452] Database query could not be executed: ISubsoft\\DAV\\CardDAV\\Backend\\LDAP::fullSyncOperation at line no 1856, SQLSTATE[HY000]: General error: 8 attempt to write a readonly database, referer: http://192.168.0.231/server.php/addressbooks/mprangen/
[Thu Jul 31 19:12:18.828919 2025] [php7:notice] [pid 13:tid 13] [client 192.168.0.26:51453] Database query could not be executed: ISubsoft\\DAV\\CardDAV\\Backend\\LDAP::fullSyncOperation at line no 1856, SQLSTATE[HY000]: General error: 8 attempt to write a readonly database, referer: http://192.168.0.231/server.php/addressbooks/mprangen/
Wenn syncdb.php ohne Probleme ausgeführt werden kann, liegt es nicht an Dateirechten bei root, sondern bei www-datat user.
wir werden das Verzeichnis nochmal neu mit rechten vergeben.
In den Container einloggen
docker-compose exec carddav /bin/bash
Dnn rechte vergeben
chown www-data:www-data -R /var/www/html/ldap-carddav/data
Ansonsten schauen wir uns mal an mit welchen umser der apache2 ausgeführt wird
ps aux | grep apache
root 1 0.0 0.0 2480 580 ? Ss 19:32 0:00 /bin/sh /usr/sbin/apachectl -D FOREGROUND
root 8 0.0 1.4 206336 28308 ? S 19:32 0:00 /usr/sbin/apache2 -D FOREGROUND
www-data 9 0.0 1.1 208744 23500 ? S 19:32 0:00 /usr/sbin/apache2 -D FOREGROUND
www-data 10 0.0 1.1 208876 24056 ? S 19:32 0:00 /usr/sbin/apache2 -D FOREGROUND
www-data 11 0.0 1.1 208876 23536 ? S 19:32 0:00 /usr/sbin/apache2 -D FOREGROUND
www-data 12 0.0 1.1 208876 23556 ? S 19:32 0:00 /usr/sbin/apache2 -D FOREGROUND
www-data 13 0.0 0.4 206376 9264 ? S 19:32 0:00 /usr/sbin/apache2 -D FOREGROUND
root 42 0.0 0.0 3240 648 pts/0 S+ 19:38 0:00 grep apache














