diff --git a/include/maxscale/utils.h b/include/maxscale/utils.h
index 0fda7a487a..71e7f9f510 100644
--- a/include/maxscale/utils.h
+++ b/include/maxscale/utils.h
@@ -182,6 +182,14 @@ bool mxs_mkdir_all(const char* path, int mask);
  */
 long get_processor_count();
 
+/**
+ * method used to get actual available num of processor even when in cpu limited docker containers,
+ * does not support cpu.share
+ *
+ * @return Number of processors or 1 if the information is not available
+ */
+long get_actual_processor_count();
+
 /**
  * Return total system memory
  *
diff --git a/server/core/config.cc b/server/core/config.cc
index 47a5a5958c..237457dedc 100644
--- a/server/core/config.cc
+++ b/server/core/config.cc
@@ -2212,7 +2212,7 @@ static int handle_global_item(const char* name, const char* value)
     {
         if (strcmp(value, CN_AUTO) == 0)
         {
-            gateway.n_threads = get_processor_count();
+            gateway.n_threads = get_actual_processor_count();
         }
         else
         {
diff --git a/server/core/utils.cc b/server/core/utils.cc
index f74af29320..2cdbdb4e07 100644
--- a/server/core/utils.cc
+++ b/server/core/utils.cc
@@ -1179,6 +1179,355 @@ long get_processor_count()
     return std::max(std::thread::hardware_concurrency(), 1U);
 }
 
+long get_cgroup_v2_process_count(char *cgroup_dir, const char *filename)
+{
+    if (cgroup_dir == NULL || filename == NULL || strlen(cgroup_dir) + strlen(filename) > PATH_MAX -1 )
+    {
+        return get_processor_count();
+    }
+    char absolute_file_name[PATH_MAX];
+    strcpy(absolute_file_name, cgroup_dir);
+    strncat(absolute_file_name, filename, PATH_MAX);
+    absolute_file_name[PATH_MAX - 1] = '\0';
+    FILE *target_file = fopen(absolute_file_name, "r");
+    if (target_file == NULL)
+    {
+        MXS_WARNING("failed to open file %s", absolute_file_name);
+        return get_processor_count();
+    }
+    char content[1000];
+    long quota = -1;
+    long period = -1;
+    while (fgets(content, PATH_MAX, target_file) != NULL)
+    {
+        if(sscanf(content, "%ld %ld", &quota, &period) != 2)
+        {
+            MXS_WARNING("failed to get max and period from file cpu.max.");
+            fclose(target_file);
+            return get_processor_count();
+        }
+    }
+    fclose(target_file);
+    if (quota == -1 || period == -1)
+    {
+        return get_processor_count();
+    }
+
+    return ceil((float)quota / period);
+}
+
+long get_cgroup_v1_value(char *cgroup_dir, const char *filename)
+{
+    if (cgroup_dir == NULL || filename == NULL || strlen(cgroup_dir) + strlen(filename) > PATH_MAX - 1)
+    {
+        return -1;
+    }
+
+    char absolute_file_name[PATH_MAX];
+    strcpy(absolute_file_name, cgroup_dir);
+    strncat(absolute_file_name, filename, PATH_MAX);
+    absolute_file_name[PATH_MAX - 1] = '\0';
+    FILE *target_file = fopen(absolute_file_name, "r");
+    if (target_file == NULL)
+    {
+        MXS_WARNING("failed to open file %s", absolute_file_name);
+        return -1;
+    }
+    char quota[1000];//the length of value is less than 1000, not the value
+    if (fgets(quota, 1000, target_file) != NULL)
+    {
+        fclose(target_file);
+        long value = atol(quota);
+        return value == 0  ? -1: value;
+    }
+    fclose(target_file);
+    return -1;
+}
+
+void get_v2_cgroup_dir(char cgroup_dir[PATH_MAX], char *mount_point, char controller_group_pathname[])
+{
+    if ( mount_point == NULL || controller_group_pathname == NULL)
+    {
+        MXS_WARNING("illegal args, will return.");
+        return;
+    }
+
+    if (strlen(mount_point) + strlen(controller_group_pathname) > PATH_MAX - 1)
+    {
+        MXS_WARNING("failed to generate cpu_cgroup_dir, which is more than %d", PATH_MAX);
+        return;
+    }
+
+    strcpy(cgroup_dir, mount_point);
+
+    if (strcmp("/", controller_group_pathname))
+    {
+        strcat(cgroup_dir, controller_group_pathname);
+    }
+    cgroup_dir[PATH_MAX - 1] = '\0';
+}
+
+void get_v1_cgroup_dir(char cgroup_dir[PATH_MAX], char *mount_root, char *mount_point, char controller_group_pathname[])
+{
+    if (mount_root == NULL || mount_point == NULL || controller_group_pathname == NULL)
+    {
+        MXS_WARNING("illegal args, will return.");
+        return;
+    }
+
+    if (strcmp(mount_root, "/") == 0)
+    {
+        strncpy(cgroup_dir, mount_point, PATH_MAX);
+        if (strcmp(controller_group_pathname, "/") != 0)
+        {
+            if (strlen(cgroup_dir) + strlen(controller_group_pathname) > PATH_MAX - 1)
+            {
+                MXS_WARNING("failed to generate cpu_cgroup_dir, which is more than %d", PATH_MAX);
+                cgroup_dir[0] = '\0';
+                return;
+            }
+            strncat(cgroup_dir, controller_group_pathname, PATH_MAX - strlen(cgroup_dir));
+        }
+        cgroup_dir[PATH_MAX - 1] = '\0';
+    }
+    else if (strcmp(controller_group_pathname, mount_root) == 0)
+    {
+        strcpy(cgroup_dir, mount_point);
+        cgroup_dir[PATH_MAX - 1] = '\0';
+    }
+    else
+    {
+        const char *p = strstr(controller_group_pathname, mount_root);
+        if (p != NULL && strcmp(controller_group_pathname, p) == 0)
+        {
+            if (strlen(mount_point) + strlen(controller_group_pathname) - strlen(mount_root) > PATH_MAX - 1)
+            {
+                MXS_WARNING("failed to generate cgroup_dir, the length of which is more than %d", PATH_MAX);
+                cgroup_dir[0] = '\0';
+                return;
+            }
+            strcpy(cgroup_dir, mount_point);
+            strncat(cgroup_dir, controller_group_pathname + strlen(mount_root), PATH_MAX - strlen(cgroup_dir));
+            cgroup_dir[PATH_MAX - 1] = '\0';
+        }
+    }
+}
+
+/**
+ * method used to get actual available num of processor even when in cpu limited docker containers
+ * parse the cgroup file as is done in openJDK
+ * Find the cgroup mount point for memory and cpuset
+ * by reading /proc/self/mountinfo and /proc/self/cgroup
+ *
+ * does not support cpu_share, which is a relative value
+ *
+ * @return actual available num of processor, equals to get_processor_count()
+ * when not in docker container
+ */
+long get_actual_processor_count()
+{
+    /**
+     * parse /proc/cgroups to find cgroup version, v1 and v2 has different hierarch setting logic, content in this file
+     * is like below:
+     * #subsys_name	hierarchy	num_cgroups	enabled
+        cpuset	5	1	1
+     */
+    FILE *cgroups = fopen("/proc/cgroups", "r");
+    if (cgroups == NULL)
+    {
+        return get_processor_count();
+    }
+
+    char content[PATH_MAX];
+    int cpu_hierarch = -1;
+    int enabled = -1;
+    char subsys_name[PATH_MAX];
+    while (fgets(content, PATH_MAX, cgroups) != NULL)
+    {
+        if (sscanf(content, "%s %d %*d %d", subsys_name, &cpu_hierarch, &enabled) != 3)
+        {
+            continue;
+        }
+
+        if (strcmp(subsys_name, "cpu") == 0)
+        {
+            break;
+        }
+
+    }
+
+    if (enabled == -1 || enabled == 0)
+    {
+        //which means cgroup is not used.
+        return get_processor_count();
+    }
+
+    bool is_cgroupv2 = cpu_hierarch == 0;
+
+    fclose(cgroups);
+
+    FILE *mount_info = fopen("/proc/self/mountinfo", "r");
+    if (mount_info == NULL)
+    {
+        MXS_WARNING("cannot open /proc/self/mountinfo, use get_processor_count instead, %s", strerror(errno));
+        return get_processor_count();
+    }
+    /**
+     * parse /proc/self/mountinfo to get cgroup path, content format is defined below:
+     * https://www.kernel.org/doc/Documentation/filesystems/proc.txt
+     * https://man7.org/linux/man-pages/man5/proc.5.html
+     */
+    char mount_root[PATH_MAX];
+    char mount_point[PATH_MAX];
+    char type[PATH_MAX];
+    char super_options[PATH_MAX];
+    char controller[PATH_MAX];
+    char controller_group_pathname[PATH_MAX];
+    char cpu_mount_root[PATH_MAX];
+    char cpu_mount_point[PATH_MAX];
+    char cpu_controller_group_pathname[PATH_MAX];
+    char cpu_cgroup_dir[PATH_MAX];
+    while (fgets(content, PATH_MAX, mount_info) != NULL)
+    {
+        // when read cgroup2, format "%*d %*d %*d:%*d %s %s %*s %*s %*s cgroup %*s %s" does not work well
+        if (sscanf(content, "%*d %*d %*d:%*d %s %s %*[^-]- %s %*s %s",
+                   mount_root, mount_point, type, super_options) != 4)
+        {
+            continue;
+        }
+
+        if (is_cgroupv2)
+        {
+            if (strcmp(type, "cgroup2") != 0)
+            {
+                MXS_DEBUG("type is not cgroup2 when cgroupv2 is used, which is %s", type);
+                continue;
+            }
+            else
+            {
+                strcpy(cpu_mount_root, mount_root);
+                strcpy(cpu_mount_point, mount_point);
+            }
+        }
+        else if (strcmp(type, "cgroup") != 0)
+        {
+            MXS_DEBUG("type is not cgroup, which is %s", type);
+            continue;
+        }
+        else
+        {
+            char *cpr = super_options;
+            char *token;
+            while ((token=strsep(&cpr, ",")) != NULL)
+            {
+                if (strcmp(token, "cpu") == 0)
+                {
+                    strcpy(cpu_mount_root, mount_root);
+                    strcpy(cpu_mount_point, mount_point);
+                    break;
+                }
+            }
+        }
+        if (strlen(cpu_mount_root) > 0)
+        {
+            break;
+        }
+    }
+    fclose(mount_info);
+    if (strlen(cpu_mount_root) == 0 || strlen(cpu_mount_point) == 0)
+    {
+        MXS_WARNING("cpu_mount_root or cpu_mount_point does not exists, use get_processor_count instead");
+        return get_processor_count();
+    }
+
+    /**
+     * if cgroupv1, should parse /proc/self/cgroup to get the relative pathname of cpu controller in cgroup, content format is defined below:
+     * https://man7.org/linux/man-pages/man7/cgroups.7.html
+     *
+     */
+    FILE *cgroup = fopen("/proc/self/cgroup", "r");
+    if (cgroup == NULL)
+    {
+        MXS_WARNING("cannot open /proc/self/mountinfo, use get_processor_count instead, %s", strerror(errno));
+        return get_processor_count();
+    }
+    if (!is_cgroupv2)
+     {
+        while (fgets(content, PATH_MAX, cgroup) != NULL)
+         {
+             if (sscanf(content, "%*[^:]:%[^:]:%s", controller, controller_group_pathname) != 2)
+             {
+                 continue;
+             }
+
+             char *cpr = controller;
+             char *token;
+
+             while ((token=strsep(&cpr, ",")) != NULL)
+             {
+                 if (strcmp(token, "cpu") == 0)
+                 {
+                     strcpy(cpu_controller_group_pathname, controller_group_pathname);
+                     break;
+                 }
+             }
+
+             if (strlen(cpu_controller_group_pathname) > 0)
+             {
+                 break;
+             }
+         }
+     }
+     else
+     {
+         /** when in cgroupV2, defined in https://man7.org/linux/man-pages/man7/cgroups.7.html
+          * for cgroupV2, only one entry is found, and format is like below:
+          *  0::/(the first field is 0, the second is empty, those are separated by colon)
+          *
+          */
+         while (fgets(content, PATH_MAX, cgroup) != NULL)
+         {
+             if (sscanf(content, "%*[^:]::%s", controller_group_pathname) == 1)
+             {
+                 strcpy(cpu_controller_group_pathname, controller_group_pathname);
+             }
+         }
+     }
+    fclose(cgroup);
+    if (strlen(cpu_controller_group_pathname) == 0)
+    {
+        MXS_WARNING("cpu_controller_group_pathname does not exists, use get_processor_count instead");
+        return get_processor_count();
+    }
+
+    if (! is_cgroupv2)
+    {
+        get_v1_cgroup_dir(cpu_cgroup_dir, cpu_mount_root, cpu_mount_point, cpu_controller_group_pathname);
+        if (strlen(cpu_cgroup_dir) == 0)
+        {
+            MXS_WARNING("does not get cpu_cgroup_dir, will use get_processor_count instead");
+            return get_processor_count();
+        }
+        long cpu_cfs_quota_us = get_cgroup_v1_value(cpu_cgroup_dir, "/cpu.cfs_quota_us");
+        if (cpu_cfs_quota_us == -1) //default value -1
+        {
+            return get_processor_count();
+        }
+        long cpu_cfs_period_us = get_cgroup_v1_value(cpu_cgroup_dir, "/cpu.cfs_period_us");
+        return ceil((float)cpu_cfs_quota_us / cpu_cfs_period_us);
+    }
+    else
+    {
+       get_v2_cgroup_dir(cpu_cgroup_dir, cpu_mount_point, cpu_controller_group_pathname);
+        if (strlen(cpu_cgroup_dir) == 0)
+        {
+            MXS_WARNING("does not get cpu_cgroup_dir, will use get_processor_count instead");
+            return get_processor_count();
+        }
+       return get_cgroup_v2_process_count(cpu_cgroup_dir, "/cpu.max");
+    }
+}
+
 int64_t get_total_memory()
 {
     int64_t pagesize = 0;
