diff --git a/check_gitlab_tokens b/check_gitlab_tokens
new file mode 100755
index 0000000000000000000000000000000000000000..c3ad8495a6ae11fcde45308a89d873923f17bf11
--- /dev/null
+++ b/check_gitlab_tokens
@@ -0,0 +1,294 @@
+#!/bin/bash
+# ======================================================================
+#
+# Check Gitlab tokens of groups and projects.
+# It warns if tokens expire soon.
+#
+# requirements:
+# - inc/rest-api-client.sh
+# - curl
+#
+# ----------------------------------------------------------------------
+# 2024-10-29  v1.0  <axel.hahn@iml.unibe.ch>
+# ======================================================================
+
+cd "$( dirname "$0" )" || exit
+. "$( dirname $0 )/inc_pluginfunctions" || exit 1
+
+export self_APPVERSION=1.0
+
+
+GITLAB_API='https://gitlab.example.com/api/v4'
+GITLAB_TOKEN='glpat-12345678'
+
+GITLAB_CONFIG=/etc/icinga2/gitlab.cfg
+REST_CLIENT="$( dirname $0 )/../inc/rest-api-client.sh"
+
+projectUrls=
+
+OUT_TOKENS=/tmp/gitlab-tokens__$USER.json
+OUT_USERS=/tmp/gitlab-users__$USER.json
+
+typeset -i iSince=395
+typeset -i iTokenCount
+typeset -i iTokensFound
+NL="
+"
+
+typeset -i iWarnLimit=30
+typeset -i iCriticalLimit=10
+
+typeset -i iCountWarn=0
+typeset -i iCountCritical=0
+
+output=""
+
+# ----------------------------------------------------------------------
+# functions
+# ----------------------------------------------------------------------
+
+# Show help
+function showHelp(){
+    local _self; _self=$(basename $0)
+cat <<EOF
+$( ph.showImlHelpHeader )
+
+Check gitlab tokens and warn if tokens expire soon.
+
+This check fetches the gitlbab tokens created in the last $iSince days
+from the Gitlab API. It skips
+
+    - personal access tokens of users
+    - revoked tokens
+
+The script can run several seconds depending on count of tokens, projects
+and users. Maybe you want to call it with a longer interval.
+
+SYNTAX:
+$_self [OPTIONS]
+
+OPTIONS:
+
+    -h or --help   show this help.
+
+    -w VALUE       warning level  (default: $iWarnLimit)
+    -c VALUE       critical level (default: $iCriticalLimit)
+
+    -g FILE        path to GITLAB_CONFIG; default: $GITLAB_CONFIG
+    -r FILE        path to REST_CLIENT; default: $REST_CLIENT
+
+    -s DAYS        Number of days for max age of token; default: $iSince
+
+PARAMETERS:
+
+    None.
+
+EXAMPLES:
+
+    $_self -w 28 -c 7
+        Set other warning and critical level
+
+    $_self -g ./gitlab.cfg
+        Set a custom gitlab config file
+
+    $_self -r /opt/bash-api-client/bash-api-client.sh
+        Set a custom gitlab config file
+
+EOF
+}
+
+# Fetch data from gitlab api with multiple page requests
+#
+# param  string  url
+# param  string  output file
+# param  int     optional: number of items per page; default: 100
+function _getPagesToFile(){
+    local url="$1"
+    local outfile="$2"
+    local iPerPage=${3:-100}
+    local page=0
+
+    test -f "${outfile}" && rm "${outfile}"
+
+    grep -q "?" <<< "$url" || url="${url}?"
+    while true; do
+        (( page++ ))
+
+        pageUrl="$url&per_page=${iPerPage}&page=${page}"
+        # echo "Request: $pageUrl"
+
+        http.makeRequest "$pageUrl"
+        if ! http.isOk > /dev/null; then
+            echo "ERROR: Request failed: $pageUrl"
+            http.getResponseHeader
+            http.getResponse
+            ph.abort
+        fi
+
+        # if response is "[]" then we are done
+        if ! http.getResponse | grep -q "^\[\]$"; then
+            http.getResponse >> "${outfile}"
+        else
+            break
+        fi
+
+    done
+}
+
+# Get web link of a project to see its tokens
+#
+# global string  projectUrls  string to cache project name + link
+#
+# param  string  username eg. projects_14_bot_nnnnnn
+# return string
+function getWeblink(){
+    local myusername="$1"
+
+    sType=$( cut -f 1 -d "_" <<< "$myusername" )s
+    # sType is one of users|projects
+
+    sId2=$( cut -f 2 -d "_" <<< "$myusername" )
+    local data
+    local myurl
+    if ! grep -q "^/$sType/$sId2:" <<< "$projectUrls" ; then
+        http.makeRequest "/$sType/$sId2"
+        if ! http.isOk > /dev/null; then
+            echo "ERROR: Request /$sType/$sId2 failed: $pageUrl"
+            http.getResponseHeader
+            http.getResponse
+            ph.abort
+        fi
+        data="$( http.getResponse )"
+        myname=$( getKey "$data" "name" )
+        myurl=$( getKey "$data" "web_url" )
+        if [ -n "$myurl" ]; then
+            projectUrls+="/$sType/$sId2:$myname <$myurl/-/settings/access_tokens>$NL"
+        fi
+    fi
+    grep "^/$sType/$sId2:" <<< "$projectUrls" | cut -f 2- -d ":"
+}
+
+# Get a single value from json
+# param  string  json data
+# param  string  key
+# return string
+function getKey(){
+    jq -r ".$2" <<< "$1" | grep -v "null"
+}
+
+# ----------------------------------------------------------------------
+# MAIN
+# ----------------------------------------------------------------------
+
+# --- check param -h
+case "$1" in
+    "--help"|"-h")
+        showHelp
+        exit 0
+        ;;
+    *)
+esac
+
+REST_CLIENT=$(   ph.getValueWithParam $REST_CLIENT   r "$@")
+GITLAB_CONFIG=$( ph.getValueWithParam $GITLAB_CONFIG g "$@")
+
+# --- check requirements
+ph.require curl
+. "${GITLAB_CONFIG}" || exit 1
+. "${REST_CLIENT}" || exit 1
+http.help >/dev/null || exit 1
+
+
+iWarnLimit=$(     ph.getValueWithParam $iWarnLimit     w "$@")
+iCriticalLimit=$( ph.getValueWithParam $iCriticalLimit c "$@")
+iSince=$(         ph.getValueWithParam $iSince         s "$@")
+
+http.init
+http.addHeader "PRIVATE-TOKEN: $GITLAB_TOKEN"
+http.setBaseUrl "$GITLAB_API"
+
+startdate="$( date +%Y-%m-%dT00:00:00Z --date "$iSince days ago")"
+sDateWarn="$( date +%Y%m%d --date "${iWarnLimit} days" )"
+sDateCritical="$( date +%Y%m%d --date "${iCriticalLimit} days" )"
+
+# get all tokens
+# see https://docs.gitlab.com/ee/api/personal_access_tokens.html
+_getPagesToFile "/personal_access_tokens/?revoked=false&created_after=${startdate}" "$OUT_TOKENS"
+
+# get all users
+# see https://docs.gitlab.com/ee/api/users.html
+_getPagesToFile "/users?exclude_humans=true&exclude_external=true&exclude_internal=true&created_after=${startdate}&search=_bot_" "$OUT_USERS"
+
+# IDs / Einträge zählen:
+iTokenCount=$( jq ".[].id " < "$OUT_TOKENS" | wc -l )
+
+iTokensFound=0
+for i in $( seq 1 $iTokenCount )
+do
+
+    # get nth token
+    entry="$( jq ".[$i]" < "$OUT_TOKENS")"
+
+    # hide non active tokens
+    if [ "$( getKey "$entry" "active" )" = "false" ]; then
+        continue
+    fi
+
+    # hide tokens without name
+    sName=$( getKey "$entry" "name" )
+    if [ -z "$sName" ]; then
+        continue
+    fi
+
+    # hide tokens referencing a username that doesn't contain "_[number]_bot_"
+    sUserid=$( getKey "$entry" "user_id" )
+    myusername="$( jq  ".[] | select(.id == $sUserid)" < "$OUT_USERS" | jq ".username" | cut -f 1-3 -d "_" | tr -d '"') "
+    if ! grep -q "_[0-9]*_bot" <<< "$myusername" ; then
+        continue
+    fi
+
+    iTokensFound+=1
+
+    # check expiration
+    sExpire=$( getKey "$entry" "expires_at" )
+
+    # remove "-" from date to get an integer
+    sExpire2=${sExpire//\-}
+
+    
+    sStatus="OK      "
+    if [ "$sExpire2" -le "$sDateWarn" ]; then
+        if [ "$sExpire2" -le "$sDateCritical" ]; then
+            iCountCritical+=1
+            sStatus="Critical"
+        else
+            iCountWarn+=1
+            sStatus="Warning "
+        fi
+    fi
+
+    myproject="$( getWeblink "$myusername" )"
+
+    output+="$sExpire  $sStatus  $sName - $myproject${NL}" 
+
+done 
+
+if [ $iCountCritical -gt 0 ]; then
+    ph.setStatus "critical"
+elif [ $iCountWarn -gt 0 ]; then
+    ph.setStatus "warning"
+else
+    ph.setStatus "ok"
+fi
+
+ph.status "$iTokensFound Gitlab Tokens (max $iSince days old) .. critical: $iCountCritical ($iCriticalLimit days) .. warnings: $iCountWarn ($iWarnLimit days)"
+echo
+
+echo "$output"
+
+# cleanup
+rm -f "$OUT_TOKENS" "$OUT_USERS"
+
+ph.exit
+
+# ----------------------------------------------------------------------
diff --git a/docs/20_Checks/_index.md b/docs/20_Checks/_index.md
index f8a7774e60f4e6ae2b0cc7ded88cdc4ac68d65c6..918714269cd546194b5af7abbff765962a9664a1 100644
--- a/docs/20_Checks/_index.md
+++ b/docs/20_Checks/_index.md
@@ -26,6 +26,7 @@ There is one include script used by all checks:
 * [check_eol](check_eol.md)
 * [check_fs_errors](check_fs_errors.md)
 * [check_fs_writable](check_fs_writable.md)
+* [check_gitlab_tokens](check_gitlab_tokens.md)
 * [check_haproxy_health](check_haproxy_health.md)
 * [check_haproxy_status](check_haproxy_status.md)
 * [check_http](check_http.md)
diff --git a/docs/20_Checks/check_gitlab_tokens.md b/docs/20_Checks/check_gitlab_tokens.md
new file mode 100644
index 0000000000000000000000000000000000000000..50ac7121dab949f53847f0a21e4c652d4415b7ce
--- /dev/null
+++ b/docs/20_Checks/check_gitlab_tokens.md
@@ -0,0 +1,115 @@
+# Check Gitlab tokens
+
+## Introduction
+
+**check_gitlab_tokens** checks all newer tokens of projects and groups if they expire soon. You can set a warning and a critical level in days.
+
+Gitlab has an api requrest `/personal_access_tokens` but it doesn't have the information about the project or usergroup where it is defined.
+This check executes additional requests to show it and offers the url to the web linkinterface.
+
+The check returns
+
+* unknown - the http request to gitlab api failed
+* critical - min. 1 token is expiring soon 
+* warning - min. 1 token reached the warning level (and no criritical token was found)
+* ok - api request was successful; no critical or warning token was found.
+
+## Requirements
+
+* curl
+* Bash REST API client<br>A set of class like functions with a http. prefix. <br>Docs: <https://os-docs.iml.unibe.ch/bash-rest-api-client/>
+
+Extract or Git pull the Bash REST API client somewhere in your filesystem. eg. /opt/bash-api-client/. With the parameter `-r <FILE>` you point to the file `rest-api-client.sh`.
+
+## Configuration
+
+The script needs to connect to the Gitlab API.
+You need to create a token in a admin group to read all tokens of all projects.
+
+Put 2 bash variabbles into `/etc/icinga2/gitlab.cfg`:
+
+```shell
+GITLAB_API='https://gitlab.example.com/api/v4'
+GITLAB_TOKEN='glpat-1234567890'
+```
+
+You can use another filename for this configuration - but then you need the parameter `-g <FILE>`to reference it.
+
+## Syntax
+
+```txt
+./check_gitlab_tokens -h
+______________________________________________________________________
+
+CHECK_GITLAB_TOKENS
+v1.0
+
+(c) Institute for Medical Education - University of Bern
+Licence: GNU GPL 3
+
+https://os-docs.iml.unibe.ch/icinga-checks/Checks/check_gitlab_tokens.html
+______________________________________________________________________
+
+Check gitlab tokens and warn if tokens expire soon.
+
+This check fetches the gitlbab tokens created in the last 395 days
+from the Gitlab API. It skips
+
+    - personal access tokens of users
+    - revoked tokens
+
+The script can run several seconds depending on count of tokens, projects
+and users. Maybe you want to call it with a longer interval.
+
+SYNTAX:
+check_gitlab_tokens [OPTIONS]
+
+OPTIONS:
+
+    -h or --help   show this help.
+
+    -w VALUE       warning level  (default: 30)
+    -c VALUE       critical level (default: 10)
+
+    -g FILE        path to GITLAB_CONFIG; default: /etc/icinga2/gitlab.cfg
+    -r FILE        path to REST_CLIENT; default: ./../inc/rest-api-client.sh
+
+    -s DAYS        Number of days for max age of token; default: 395
+
+PARAMETERS:
+
+    None.
+
+EXAMPLES:
+
+    check_gitlab_tokens -w 28 -c 7
+        Set other warning and critical level
+
+    check_gitlab_tokens -g ./gitlab.cfg
+        Set a custom gitlab config file
+
+    check_gitlab_tokens -r /opt/bash-api-client/bash-api-client.sh
+        Set a custom gitlab config file
+
+```
+
+## Example
+
+The execution of  `check_gitlab_tokens` returns
+
+* a status line with found tokens total, count of warning and critical
+* one line per token with
+  * date of expiration
+  * status; one of OK, warning, critical based on number of days before expiring
+  * name of thwe token
+  * name of the project or group
+  * web link to the token page of the project or group
+
+```text
+OK: 16 Gitlab Tokens (max 395 days old) .. critical: 0 (10 days) .. warnings: 0 (30 days)
+
+2025-01-17  OK        changelog - demoproject <https://gitlab.example.com/test/demoproject/-/settings/access_tokens>
+2025-01-23  OK        read_repo - demoproject <https://gitlab.example.com/test/demoproject/-/settings/access_tokens>
+2025-03-14  OK        api_token - admin <https://gitlab.example.com/admin/sysadminstuff/-/settings/access_tokens>
+...
+```
\ No newline at end of file
diff --git a/docs/style.css b/docs/style.css
index 4186d17f75909f2d9190cff5152d48366c829baf..b5afea45f18c3f10ab029eaca933088ee78d308d 100644
--- a/docs/style.css
+++ b/docs/style.css
@@ -1,28 +1,30 @@
 /*
     override css elements of daux.io blue theme
-    version 2023-10-09
+    version 2024-10-24
 */
 :root {
     /* Axels Overrides */
     --color-text: #234;
-    --link-color: #822;
+    --link-color: #228;
     --brand-color: var(--color-text);
     --brand-background: var(--body-background);
+    --code-tag-background-color: #f0f3f3;
+    --code-tag-border-color: #dee;
+    --code-tag-box-shadow: none;
     --hr-color: none;
+    --pager-background-color: #f8fafa;
+    --pager-border-color: none;
     --search-field-background: none;
     --search-field-border-color: none;
     --sidebar-background: var(--body-background);
     --sidebar-border-color: none;
-    --sidebar-link-active-background: #e8f4f6;
-    --sidebar-link-active-background: #eee;
+    --sidebar-link-active-background: #f0f4f6;
     /* Axels custom values */
     --axel_bg-toc: var(--body-background);
     --axel_bg-toc-head: #f8f8f8;
     --axel_brand-background: none;
     --axel_brand-pre-background: rgb(255, 0, 51);
-    ;
     --axel_brand-pre-background-hover: rgb(255, 0, 51);
-    ;
     --axel_h1_header: none;
     --axel_h1: #111;
     --axel_h1-bg: none;
@@ -33,12 +35,13 @@
     --axel_h2-hero-bottom: 2px solid #912;
     --axel_h3: #333;
     --axel_h3-bottom: 0px solid #ddd;
-    --axel_h4: #444;
-    --axel_hero_bg: #f8f8f8;
+    --axel_h4: #478;
+    --axel_h5: #699;
+    --axel_hero_bg: #faf8f6;
     --axel_img-border: 2px dashed #ccc;
     --axel_nav-bg: #fcfcfc;
     --axel_nav-buttomborder: #ddd;
-    --axel_pre-background: #f8f8f8;
+    --axel_pre-background: #faf8f6;
     --axel-th-background: #e0e4e8;
     --axel-article-nav-border-top: 0px dotted #ddd;
 }
@@ -46,9 +49,10 @@
 .dark {
     /* Axels Overrides */
     --color-text: #c0c0c0;
-    --link-color: #c66;
+    --link-color: #88e;
     --brand-color: var(--color-text);
     --brand-background: var(--body-background);
+    --body-background: #101418;
     --hr-color: none;
     --code-tag-background-color_: #bcc;
     --search-field-background: none;
@@ -74,6 +78,8 @@
     --axel_h2-hero-bottom: 2px solid #712;
     --axel_h3: #589;
     --axel_h3-bottom: 0px solid #333;
+    --axel_h4: #478;
+    --axel_h5: #278;
     --axel_hero_bg: #242424;
     --axel_img-border: 2px dashed #555;
     --axel_nav-bg: #242424;
@@ -123,6 +129,17 @@ a.Brand {
     padding-top: 1em;
 }
 
+.s-content h1::before{
+    background: #fee;
+    border: 3px double #f00;
+    color: #f00;
+    content: 'FEHLER: Keine Überschrift 1 in einer Markdown-Datei für Daux verwenden! Mit H2 beginnen!';
+    display: block;
+    font-size: 50%;
+    padding: 0.3em;
+    margin-bottom: 2em;
+}
+
 .s-content h1 {
     background: var(--axel_h1-bg);
     color: var(--axel_h1);
@@ -168,6 +185,13 @@ img{
 
 .s-content > h4 {
     color: var(--axel_h4);
+    font-size: 140%;
+    font-weight: bold;
+    margin: 2em 0;
+}
+
+.s-content > h5 {
+    color: var(--axel_h5);
     font-size: 135%;
     font-weight: bold;
     margin: 2em 0;
@@ -175,10 +199,12 @@ img{
 
 .s-content .TableOfContentsContainer h4 {
     margin: 1em 0;
-    font-size: 100%;
+    font-size: 110%;
     text-align: center;
-    background-color: rgba(0, 0, 0, 0.05);
+    background-color: rgba(0, 0, 0, 0.1);
     padding: 0.3em;
+    font-weight: bold;
+    font-family: Arial;
 }
 ul.TableOfContents a{
     color: var(--color-text);
@@ -241,6 +267,7 @@ div.hero h2 {
         position: fixed;
         right: 2em;
         top: 1em;
+        height: 96%;
     }
 }
 
@@ -254,12 +281,14 @@ div.hero h2 {
     border-top-left-radius: 1em;
     font-size: 1.1em;
     margin: 0;
-    padding: 0;
+    padding: 0.3em;
 }
 
 .TableOfContentsContainer__content {
-    border-width: 1px;
+    border-width: 0px;
     font-size: 0.5em;
+    height: inherit; 
+    overflow: auto;
 }
 
 ul.TableOfContents ul {
@@ -267,17 +296,17 @@ ul.TableOfContents ul {
     padding-left: 1em;
 }
 
+.TableOfContents a:hover{
+    text-decoration: underline;
+}
+
 /* ----- Icons on links --- */
 
 .EditOn a::before{
     content: '✏️ ';
 }
 
-.Links a[href^="https://github.com/"]::before {
-    content: '🌐 ';
-}
-
-.Links a[href^="https://git-repo.iml.unibe.ch/"]::before {
+.Links a::before {
     content: '🌐 ';
 }