diff --git a/.dockerignore b/.dockerignore
index d0d9e8e37c4840a9cd49cc90b21142ba7f6c80f6..34bd8d4ebe562836a82028affccc26a2b34a9eb2 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -41,7 +41,9 @@ gui/public/env.js
 !examples/plugins/parser/nomad.yaml
 
 # Ignore built gui and docs artufacts
-nomad/app/static/
+nomad/app/static/.gui_configured
+nomad/app/static/docs
+nomad/app/static/gui
 
 # Ignore files created at nomad runtime
 run/
diff --git a/.gitignore b/.gitignore
index 98aad82dcb783db470fbf0558ae087d682e35af1..750bb9d46975bd201d1d9b8c17b37ea659ef33f7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,7 +24,9 @@ gui/public/artifacts.js
 !ops/docker-compose/nomad-oasis-with-keycloak/configs/nomad.yaml
 
 # Ignore built gui and docs artifacts
-nomad/app/static/
+nomad/app/static/.gui_configured
+nomad/app/static/docs
+nomad/app/static/gui
 
 nomad/normalizing/data/*.db
 nomad/normalizing/data/*.msg
@@ -81,6 +83,7 @@ share/python-wheels/
 .installed.cfg
 *.egg
 MANIFEST
+nexus.obj
 
 # PyInstaller
 #  Usually these files are written by a python script from a template
diff --git a/nomad/app/main.py b/nomad/app/main.py
index e8304c542d5e8711040b27f5cb5c95808d2406b8..2d3dd00ff420b0696e6e03d840b885c542c69db7 100644
--- a/nomad/app/main.py
+++ b/nomad/app/main.py
@@ -16,31 +16,21 @@
 # limitations under the License.
 #
 
-import re
-import os
 import hashlib
 import json
 
-from fastapi import FastAPI, Request, Response, status
+from fastapi import FastAPI, Response, status
 from fastapi.exception_handlers import (
     http_exception_handler as default_http_exception_handler,
 )
 from starlette.exceptions import HTTPException as StarletteHTTPException
 from fastapi.responses import HTMLResponse, JSONResponse
-from starlette.staticfiles import (
-    StaticFiles as StarletteStaticFiles,
-    NotModifiedResponse,
-)
 from starlette.middleware.base import BaseHTTPMiddleware
-from starlette.responses import PlainTextResponse
-from starlette.datastructures import Headers
 from pydantic import BaseModel
 
 from nomad import config, infrastructure
 from .v1.main import app as v1_app
-
-from nomad.cli.dev import get_gui_artifacts_js
-from nomad.cli.dev import get_gui_config
+from .static import app as static_files_app, GuiFiles
 
 
 class OasisAuthenticationMiddleware(BaseHTTPMiddleware):
@@ -69,6 +59,18 @@ class OasisAuthenticationMiddleware(BaseHTTPMiddleware):
 app = FastAPI()
 
 app_base = config.services.api_base_path
+
+
+@app.get(f'{app_base}/alive')
+async def alive():
+    return 'I am, alive!'
+
+
+@app.get('/-/health', status_code=status.HTTP_200_OK)
+async def health():
+    return {'healthcheck': 'ok'}
+
+
 app.mount(f'{app_base}/api/v1', v1_app)
 
 if config.services.optimade_enabled:
@@ -93,111 +95,8 @@ if config.resources.enabled:
 
     app.mount(f'{app_base}/resources', resources_app)
 
-dist_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../dist'))
-docs_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), 'static/docs'))
-gui_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), 'static/gui'))
-if not os.path.exists(gui_folder):
-    os.makedirs(gui_folder)
-
-configured_gui_folder = os.path.join(
-    config.fs.working_directory, 'run', 'gui_configured'
-)
-if os.path.exists(configured_gui_folder):
-    gui_folder = configured_gui_folder
-
-
-class StaticFiles(StarletteStaticFiles):
-    etag_re = r'^(W/)?"?([^"]*)"?$'
-
-    def is_not_modified(
-        self, response_headers: Headers, request_headers: Headers
-    ) -> bool:
-        # The starlette etag implementation is not considering the "..." and W/"..." etag
-        # RFC syntax used by browsers.
-        try:
-            if_none_match = request_headers['if-none-match']
-            match = re.match(StaticFiles.etag_re, if_none_match)
-            if_none_match = match.group(2)
-            etag = response_headers['etag']
-            if if_none_match == etag:
-                return True
-        except KeyError:
-            pass
-
-        return super().is_not_modified(response_headers, request_headers)
-
-
-class GuiFiles(StaticFiles):
-    gui_artifacts_data = None
-    gui_env_data = None
-    gui_data_etag = None
-
-    async def get_response(self, path: str, scope) -> Response:
-        if path not in ['env.js', 'artifacts.js']:
-            response = await super().get_response(path, scope)
-        else:
-            assert (
-                GuiFiles.gui_data_etag is not None
-            ), 'Etag for gui data was not initialized'
-            response = PlainTextResponse(
-                GuiFiles.gui_env_data
-                if path == 'env.js'
-                else GuiFiles.gui_artifacts_data,
-                media_type='application/javascript',
-                headers=dict(etag=GuiFiles.gui_data_etag),
-            )
-
-        request_headers = Headers(scope=scope)
-        if self.is_not_modified(response.headers, request_headers):
-            return NotModifiedResponse(response.headers)
-        return response
-
-
-app.mount(
-    f'{app_base}/dist',
-    StaticFiles(directory=dist_folder, check_dir=False),
-    name='dist',
-)
-app.mount(
-    f'{app_base}/docs', StaticFiles(directory=docs_folder, check_dir=False), name='docs'
-)
-app.mount(
-    f'{app_base}/gui', GuiFiles(directory=gui_folder, check_dir=False), name='gui'
-)
-
-
-@app.on_event('startup')
-async def startup_event():
-    from nomad.cli.dev import get_gui_artifacts_js
-    from nomad.cli.dev import get_gui_config
-    from nomad import infrastructure
-    from nomad.parsing.parsers import import_all_parsers
-    from nomad.metainfo.elasticsearch_extension import entry_type
-
-    import_all_parsers()
-
-    # each subprocess is supposed disconnect and
-    # connect again: https://jira.mongodb.org/browse/PYTHON-2090
-    try:
-        from mongoengine import disconnect
-
-        disconnect()
-    except Exception:
-        pass
-
-    entry_type.reload_quantities_dynamic()
-    GuiFiles.gui_artifacts_data = get_gui_artifacts_js()
-    GuiFiles.gui_env_data = get_gui_config()
-
-    data = {
-        'artifacts': GuiFiles.gui_artifacts_data,
-        'gui_config': GuiFiles.gui_env_data,
-    }
-    GuiFiles.gui_data_etag = hashlib.md5(
-        json.dumps(data).encode(), usedforsecurity=False
-    ).hexdigest()
-
-    infrastructure.setup()
+# Make sure to mount this last, as it is a catch-all routes that are not yet mounted.
+app.mount(app_base, static_files_app)
 
 
 @app.exception_handler(StarletteHTTPException)
@@ -255,45 +154,35 @@ async def http_exception_handler(request, exc):
     )
 
 
-@app.get(f'{app_base}/alive')
-async def alive():
-    """Simple endpoint to utilize kubernetes liveness/readiness probing."""
-    return 'I am, alive!'
-
-
-@app.get('/-/health', status_code=status.HTTP_200_OK)
-async def health():
-    return {'healthcheck': 'ok'}
-
-
-max_cache_ages = {
-    r'\.[a-f0-9]+\.chunk\.(js|css)$': 3600 * 24 * 7,
-    r'\.(html|js|css)$': config.services.html_resource_http_max_age,
-    r'\.(png|jpg|gif|jpeg|ico)$': config.services.image_resource_http_max_age,
-}
+@app.on_event('startup')
+async def startup_event():
+    from nomad.cli.dev import get_gui_artifacts_js
+    from nomad.cli.dev import get_gui_config
+    from nomad.parsing.parsers import import_all_parsers
+    from nomad import infrastructure
+    from nomad.metainfo.elasticsearch_extension import entry_type
 
+    import_all_parsers()
 
-@app.middleware('http')
-async def add_header(request: Request, call_next):
-    response = await call_next(request)
+    # each subprocess is supposed disconnect and
+    # connect again: https://jira.mongodb.org/browse/PYTHON-2090
+    try:
+        from mongoengine import disconnect
 
-    max_age = None
-    for key, value in max_cache_ages.items():
-        if re.search(key, str(request.url)):
-            max_age = value
-            break
+        disconnect()
+    except Exception:
+        pass
 
-    if max_age is not None:
-        response.headers['Cache-Control'] = f'max-age={max_age}, must-revalidate'
-    else:
-        response.headers[
-            'Cache-Control'
-        ] = f'max-age=0, no-cache, no-store, must-revalidate'
+    entry_type.reload_quantities_dynamic()
+    GuiFiles.gui_artifacts_data = get_gui_artifacts_js()
+    GuiFiles.gui_env_data = get_gui_config()
 
-    # The etags that we and starlette produce do not follow the RFC, because they do not
-    # start with a " as the RFC specifies. Nginx considers them weak etags and will strip
-    # these if gzip is enabled.
-    if not response.headers.get('etag', '"').startswith('"'):
-        response.headers['etag'] = f'"{response.headers.get("etag")}"'
+    data = {
+        'artifacts': GuiFiles.gui_artifacts_data,
+        'gui_config': GuiFiles.gui_env_data,
+    }
+    GuiFiles.gui_data_etag = hashlib.md5(
+        json.dumps(data).encode(), usedforsecurity=False
+    ).hexdigest()
 
-    return response
+    infrastructure.setup()
diff --git a/nomad/app/static/__init__.py b/nomad/app/static/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..877bd1f8f12195f961e0a69e97d8f572fd0edb31
--- /dev/null
+++ b/nomad/app/static/__init__.py
@@ -0,0 +1,115 @@
+import re
+import os
+
+from fastapi import FastAPI, Request, Response
+from fastapi.exception_handlers import (
+    http_exception_handler as default_http_exception_handler,
+)
+from starlette.staticfiles import (
+    StaticFiles as StarletteStaticFiles,
+    NotModifiedResponse,
+)
+from starlette.responses import PlainTextResponse
+from starlette.datastructures import Headers
+
+from nomad import config
+
+
+app = FastAPI()
+
+docs_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), 'docs'))
+gui_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), 'gui'))
+if not os.path.exists(gui_folder):
+    os.makedirs(gui_folder)
+
+configured_gui_folder = os.path.join(
+    config.fs.working_directory, 'run', 'gui_configured'
+)
+if os.path.exists(configured_gui_folder):
+    gui_folder = configured_gui_folder
+
+
+class StaticFiles(StarletteStaticFiles):
+    etag_re = r'^(W/)?"?([^"]*)"?$'
+
+    def is_not_modified(
+        self, response_headers: Headers, request_headers: Headers
+    ) -> bool:
+        # The starlette etag implementation is not considering the "..." and W/"..." etag
+        # RFC syntax used by browsers.
+        try:
+            if_none_match = request_headers['if-none-match']
+            match = re.match(StaticFiles.etag_re, if_none_match)
+            if_none_match = match.group(2)
+            etag = response_headers['etag']
+            if if_none_match == etag:
+                return True
+        except KeyError:
+            pass
+
+        return super().is_not_modified(response_headers, request_headers)
+
+
+app.mount(f'/docs', StaticFiles(directory=docs_folder, check_dir=False), name='docs')
+
+
+class GuiFiles(StaticFiles):
+    gui_artifacts_data = None
+    gui_env_data = None
+    gui_data_etag = None
+
+    async def get_response(self, path: str, scope) -> Response:
+        if path not in ['env.js', 'artifacts.js']:
+            response = await super().get_response(path, scope)
+        else:
+            assert (
+                GuiFiles.gui_data_etag is not None
+            ), 'Etag for gui data was not initialized'
+            response = PlainTextResponse(
+                GuiFiles.gui_env_data
+                if path == 'env.js'
+                else GuiFiles.gui_artifacts_data,
+                media_type='application/javascript',
+                headers=dict(etag=GuiFiles.gui_data_etag),
+            )
+
+        request_headers = Headers(scope=scope)
+        if self.is_not_modified(response.headers, request_headers):
+            return NotModifiedResponse(response.headers)
+        return response
+
+
+gui_files_app = GuiFiles(directory=gui_folder, check_dir=False)
+app.mount(f'/gui', gui_files_app, name='gui')
+
+max_cache_ages = {
+    r'\.[a-f0-9]+\.chunk\.(js|css)$': 3600 * 24 * 7,
+    r'\.(html|js|css)$': config.services.html_resource_http_max_age,
+    r'\.(png|jpg|gif|jpeg|ico)$': config.services.image_resource_http_max_age,
+}
+
+
+@app.middleware('http')
+async def add_header(request: Request, call_next):
+    response = await call_next(request)
+
+    max_age = None
+    for key, value in max_cache_ages.items():
+        if re.search(key, str(request.url)):
+            max_age = value
+            break
+
+    if max_age is not None:
+        response.headers['Cache-Control'] = f'max-age={max_age}, must-revalidate'
+    else:
+        response.headers[
+            'Cache-Control'
+        ] = f'max-age=0, no-cache, no-store, must-revalidate'
+
+    # The etags that we and starlette produce do not follow the RFC, because they do not
+    # start with a " as the RFC specifies. Nginx considers them weak etags and will strip
+    # these if gzip is enabled.
+    if not response.headers.get('etag', '"').startswith('"'):
+        response.headers['etag'] = f'"{response.headers.get("etag")}"'
+
+    return response
diff --git a/nomad/app/v1/main.py b/nomad/app/v1/main.py
index ee31bb1acea0c31ca0c8d9fb97f2a94e559c5135..2cdcd5e3a61eb8ff7127f31ba560839de5c396ee 100644
--- a/nomad/app/v1/main.py
+++ b/nomad/app/v1/main.py
@@ -16,10 +16,12 @@
 # limitations under the License.
 #
 
-from typing import Any
+from typing import Any, cast
 from fastapi import FastAPI, status, Request
 from fastapi.middleware.cors import CORSMiddleware
 from fastapi.responses import JSONResponse, RedirectResponse
+from starlette.types import ASGIApp, Message, Receive, Scope, Send
+from fastapi.routing import APIRoute
 import traceback
 import orjson
 
@@ -90,10 +92,16 @@ async def redirect_to_docs(req: Request):
 app.add_route('/', redirect_to_docs, include_in_schema=False)
 
 
-@app.middleware('http')
-async def log_request_time(request: Request, call_next):
-    with utils.timer(logger, 'request handled', url=request.url.path):
-        return await call_next(request)
+class LoggingMiddleware:
+    def __init__(self, app: ASGIApp) -> None:
+        self.app = app
+
+    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
+        with utils.timer(logger, 'request handled', path=scope.get('path')):
+            await self.app(scope, receive, send)
+
+
+app.add_middleware(LoggingMiddleware)
 
 
 @app.exception_handler(Exception)
diff --git a/nomad/app/v1/routers/entries.py b/nomad/app/v1/routers/entries.py
index 7d3ccdd32b10ae60a744ce4b0330de9a9e45c315..acf99304a23e9ac0cefcc44efb0443533f61f416 100644
--- a/nomad/app/v1/routers/entries.py
+++ b/nomad/app/v1/routers/entries.py
@@ -38,7 +38,6 @@ import json
 import orjson
 from pydantic.main import create_model
 from starlette.responses import Response
-from joblib import Parallel, delayed, parallel_backend
 
 from nomad import files, config, utils, metainfo, processing as proc
 from nomad import datamodel
@@ -857,7 +856,7 @@ def _validate_required(required: ArchiveRequired, user) -> RequiredReader:
 def _read_entry_from_archive(entry: dict, uploads, required_reader: RequiredReader):
     entry_id, upload_id = entry['entry_id'], entry['upload_id']
 
-    # all other exceptions are handled by the caller `_read_entries_from_archive`
+    # all other exceptions are handled by the caller `_answer_entries_archive_request`
     try:
         upload_files = uploads.get_upload_files(upload_id)
 
@@ -872,27 +871,8 @@ def _read_entry_from_archive(entry: dict, uploads, required_reader: RequiredRead
         return None
 
 
-def _read_entries_from_archive(
-    entries: Union[list, dict], required: ArchiveRequired, user
-):
-    """
-    Takes pickleable arguments so that it can be offloaded to worker processes.
-
-    It is important to ensure the return values are also pickleable.
-    """
-    with _Uploads() as uploads:
-        required_reader = _validate_required(required, user)
-
-        if isinstance(entries, dict):
-            return _read_entry_from_archive(entries, uploads, required_reader)
-
-        return [
-            _read_entry_from_archive(entry, uploads, required_reader)
-            for entry in entries
-        ]
-
-
-def _answer_entries_archive_request(
+async def _answer_entries_archive_request(
+    request: Request,
     owner: Owner,
     query: Query,
     pagination: MetadataPagination,
@@ -928,27 +908,29 @@ def _answer_entries_archive_request(
         for entry in search_response.data
     ]
 
-    # fewer than config.archive.min_entries_per_process entries per process is not useful
-    # more than config.max_process_number processes is too much for the server
-    number: int = min(
-        int(math.ceil(len(entries) / config.archive.min_entries_per_process)),
-        config.archive.max_process_number,
-    )
+    required_reader = _validate_required(required, user)
+    response_data = []
+    if isinstance(entries, dict):
+        entries = [entries]
 
-    if number <= 1:
-        request_data: list = _read_entries_from_archive(entries, required, user)
-    else:
-        with parallel_backend('threading', n_jobs=number):
-            request_data = Parallel()(
-                delayed(_read_entries_from_archive)(i, required, user) for i in entries
-            )
+    with _Uploads() as uploads:
+        for entry in entries:
+            disconnected = await request.is_disconnected()
+            if disconnected:
+                logger.info('client disconnected', endpoint='entries/archive')
+                break
+
+            entry_archive = _read_entry_from_archive(entry, uploads, required_reader)
+            response_data.append(entry_archive)
+
+        logger.info('read all archives', endpoint='entries/archive')
 
     return EntriesArchiveResponse(
         owner=search_response.owner,
         query=search_response.query,
         pagination=search_response.pagination,
         required=required,
-        data=list(filter(None, request_data)),
+        data=list(filter(None, response_data)),
     )
 
 
@@ -977,7 +959,8 @@ async def post_entries_archive_query(
     data: EntriesArchive,
     user: User = Depends(create_user_dependency()),
 ):
-    return _answer_entries_archive_request(
+    return await _answer_entries_archive_request(
+        request=request,
         owner=data.owner,
         query=data.query,
         pagination=data.pagination,
@@ -1002,7 +985,8 @@ async def get_entries_archive_query(
     pagination: MetadataPagination = Depends(metadata_pagination_parameters),
     user: User = Depends(create_user_dependency()),
 ):
-    res = _answer_entries_archive_request(
+    res = await _answer_entries_archive_request(
+        request=request,
         owner=with_query.owner,
         query=with_query.query,
         pagination=pagination,
diff --git a/nomad/app/v1/routers/metainfo.py b/nomad/app/v1/routers/metainfo.py
index 798c85386c474e82cec7bf58dd3a38295ca6aed0..2d9471828698ae3be582c3efec4b33539112fc5a 100644
--- a/nomad/app/v1/routers/metainfo.py
+++ b/nomad/app/v1/routers/metainfo.py
@@ -103,7 +103,7 @@ def store_package_definition(package: Package, **kwargs):
         ).count()
         > 0
     ):
-        logger.info(f'Package {package.definition_id} already exists. Skipping.')
+        logger.info(f'Package already exists.', package_id={package.definition_id})
         return
 
     mongo_package = PackageDefinition(package, **kwargs)
diff --git a/nomad/app/v1/routers/north.py b/nomad/app/v1/routers/north.py
index 1ca98b1343d80671237b7531e70d05189750610b..be57d7eab21fca13596818e7d5118e6b70543ce8 100644
--- a/nomad/app/v1/routers/north.py
+++ b/nomad/app/v1/routers/north.py
@@ -255,7 +255,7 @@ async def start_tool(
         'external_mounts': external_mounts,
     }
 
-    logger.info('body of the post call', body=body)
+    logger.info('post tool start to jupyterhub', body=body)
 
     response = requests.post(url, json=body, headers=hub_api_headers)
 
diff --git a/nomad/app/v1/routers/uploads.py b/nomad/app/v1/routers/uploads.py
index 8bbecd4900dca3a6b628fac5124d54db687e9173..95d476877c3aa4e150310a0eb2c4b57208163b9e 100644
--- a/nomad/app/v1/routers/uploads.py
+++ b/nomad/app/v1/routers/uploads.py
@@ -2515,11 +2515,11 @@ async def _get_files_if_provided(
                         f.write(chunk)
                         if uploaded_bytes > next_log_at:
                             logger.info(
-                                'Large upload in progress - uploaded: '
-                                f'{uploaded_bytes // log_interval} {log_unit}'
+                                'large upload in progress',
+                                uploaded_bytes=uploaded_bytes,
                             )
                             next_log_at += log_interval
-                    logger.info(f'Uploaded {uploaded_bytes} bytes')
+                    logger.info(f'upload completed', uploaded_bytes={uploaded_bytes})
             except Exception as e:
                 if not (isinstance(e, RuntimeError) and 'Stream consumed' in str(e)):
                     if os.path.exists(tmp_dir):
@@ -2536,7 +2536,7 @@ async def _get_files_if_provided(
             shutil.rmtree(tmp_dir)
             return [], None
 
-    logger.info(f'received {len(upload_paths)} uploaded file(s)')
+    logger.info(f'received uploaded file(s)')
     if method == 2 and no_file_name_info_provided:
         # Only ok if uploaded file is a zip or a tar archive.
         ext = (
diff --git a/nomad/client/archive.py b/nomad/client/archive.py
index 75dc689c42b3adae23dc2d32e1e93653bb0141b3..911c7734c9e65b74de341becd004d665b533abf5 100644
--- a/nomad/client/archive.py
+++ b/nomad/client/archive.py
@@ -21,6 +21,8 @@ import asyncio
 from asyncio import Semaphore
 from itertools import islice
 from typing import Any, Union
+from math import floor
+from time import monotonic
 import threading
 
 from click import progressbar
@@ -116,6 +118,9 @@ class ArchiveQuery:
 
     Setting a high value for `semaphore` may cause the server to return 500, 502, 504 errors.
 
+    Use `max_requests_per_second` to control the number of requests per second in case the server
+    has a rate limit.
+
     Params:
         owner (str): ownership scope
         query (dict): query
@@ -131,6 +136,7 @@ class ArchiveQuery:
         retry (int): number of retry when fetching uploads, default: 4
         sleep_time (float): sleep time for retry, default: 4.
         semaphore (int): number of concurrent downloads, this depends on server settings, default: 4
+        max_requests_per_second (int): maximum requests per second, default: 999999
     """
 
     def __init__(
@@ -145,10 +151,11 @@ class ArchiveQuery:
         batch_size: int = 10,
         username: str = None,
         password: str = None,
-        retry: int = 4,
+        retry: int = 1,
         sleep_time: float = 4.0,
         from_api: bool = False,
-        semaphore: int = 4,
+        semaphore: int = 8,
+        max_requests_per_second: int = 20,
     ):
         self._owner: str = owner
         self._required = required if required else dict(run='*')
@@ -168,6 +175,10 @@ class ArchiveQuery:
         self._sleep_time: float = sleep_time if sleep_time > 0.0 else 4.0
         self._semaphore = min(10, semaphore) if semaphore > 0 else 4
         self._results_actual: int = 0
+        self._max_requests_per_second: int = max_requests_per_second
+
+        self._start_time: float = 0.0
+        self._accumulated_requests: int = 0
 
         from nomad.client import Auth
 
@@ -262,6 +273,15 @@ class ArchiveQuery:
 
         return self._results_max
 
+    @property
+    def _allowed_requests(self) -> float:
+        """
+        The number of requests allowed since the start of the download.
+        This is controlled by the maximum number of requests per second.
+        """
+        duration: float = monotonic() - self._start_time
+        return self._max_requests_per_second * duration
+
     async def _fetch_async(self, number: int) -> int:
         """
         There is no need to perform fetching asynchronously as the required number of uploads
@@ -371,6 +391,9 @@ class ArchiveQuery:
 
         actual_number: int = min(number, len(self._entries))
 
+        self._start_time = monotonic()
+        self._accumulated_requests = 0
+
         def batched(iterable, chunk_size):
             iterator = iter(iterable)
             while chunk := list(islice(iterator, chunk_size)):
@@ -413,6 +436,11 @@ class ArchiveQuery:
         request = self._download_request(entry_ids)
 
         async with semaphore:
+            while self._accumulated_requests > self._allowed_requests:
+                await asyncio.sleep(0.1)
+
+            self._accumulated_requests += 1
+
             response = await session.post(
                 self._download_url, json=request, headers=self._auth.headers()
             )
diff --git a/nomad/config/models.py b/nomad/config/models.py
index 177cbdd397bd996b96e205b31fbb34e28a0de41d..f30c8c79dd96c0846547b1f86b51d051fe469321 100644
--- a/nomad/config/models.py
+++ b/nomad/config/models.py
@@ -917,13 +917,6 @@ class Archive(NomadSettings):
         description='GPFS needs at least 256K to achieve decent performance.',
     )
     toc_depth = Field(6, description='Depths of table of contents in the archive.')
-    max_process_number = Field(
-        20,
-        description='Maximum number of processes can be assigned to process archive query.',
-    )
-    min_entries_per_process = Field(
-        20, description='Minimum number of entries per process.'
-    )
     use_new_writer = False  # todo: to be removed
     small_obj_optimization_threshold = Field(
         256 * 1024,
diff --git a/ops/kubernetes/nomad-prod-develop.yaml b/ops/kubernetes/nomad-prod-develop.yaml
index 489d39e57cb9297e8da2fec252909c3917058dc1..52e600b2d05638b355a537091919e74b28a64034 100644
--- a/ops/kubernetes/nomad-prod-develop.yaml
+++ b/ops/kubernetes/nomad-prod-develop.yaml
@@ -6,10 +6,6 @@ nomad:
       usesBetaData: false
       officialUrl: "https://nomad-lab.eu/prod/v1/gui"
 
-    proxy:
-      external:
-        path: "/prod/v1/develop"
-
     gui:
       debug: true
 
@@ -29,16 +25,8 @@ nomad:
   image:
     tag: "prod"
 
-  ingress:
-    hosts:
-      - host: cloud.nomad-lab.eu
-        paths:
-          - path: /prod/v1/develop/
-            pathType: ImplementationSpecific
-      - host: nomad-lab.eu
-        paths:
-          - path: /prod/v1/develop/
-            pathType: ImplementationSpecific
+  proxy:
+    path: "/prod/v1/develop"
 
   app:
     replicaCount: 4
diff --git a/ops/kubernetes/nomad-prod-staging.yaml b/ops/kubernetes/nomad-prod-staging.yaml
index da4e78fe8562f1ef1e023181b1a411b71e090a14..97ffefc57dbd64f99cf0a5f16f7eac34a3b21314 100644
--- a/ops/kubernetes/nomad-prod-staging.yaml
+++ b/ops/kubernetes/nomad-prod-staging.yaml
@@ -6,10 +6,6 @@ nomad:
       usesBetaData: false
       officialUrl: "https://nomad-lab.eu/prod/v1/gui"
 
-    proxy:
-      external:
-        path: "/prod/v1/staging"
-
     gui:
       debug: true
 
@@ -29,22 +25,14 @@ nomad:
   image:
     tag: "prod"
 
-  ingress:
-    hosts:
-      - host: cloud.nomad-lab.eu
-        paths:
-          - path: /prod/v1/staging/
-            pathType: ImplementationSpecific
-      - host: nomad-lab.eu
-        paths:
-          - path: /prod/v1/staging/
-            pathType: ImplementationSpecific
+  proxy:
+    path: "/prod/v1/staging"
 
   app:
     replicaCount: 8
 
   worker:
-    replicaCount: 1
+    replicaCount: 2
     processes: 12
     resources:
       limits:
diff --git a/ops/kubernetes/nomad-prod-test.yaml b/ops/kubernetes/nomad-prod-test.yaml
index b9a7651c073ada2bac63f45a7f862364c1f409f2..e5666d1f468feb6029f3929459fedb19acde5238 100644
--- a/ops/kubernetes/nomad-prod-test.yaml
+++ b/ops/kubernetes/nomad-prod-test.yaml
@@ -6,10 +6,6 @@ nomad:
       usesBetaData: true
       officialUrl: "https://nomad-lab.eu/prod/v1/gui"
 
-    proxy:
-      external:
-        path: "/prod/v1/test"
-
     gui:
       debug: true
 
@@ -38,16 +34,8 @@ nomad:
   image:
     tag: "prod"
 
-  ingress:
-    hosts:
-      - host: cloud.nomad-lab.eu
-        paths:
-          - path: /prod/v1/test/
-            pathType: ImplementationSpecific
-      - host: nomad-lab.eu
-        paths:
-          - path: /prod/v1/test/
-            pathType: ImplementationSpecific
+  proxy:
+    path: "/prod/v1/test"
 
   app:
     replicaCount: 4
diff --git a/ops/kubernetes/nomad-prod-util.yaml b/ops/kubernetes/nomad-prod-util.yaml
index d05a929d453a421789e63497bcfae4d32d00b425..4966a53dd7266b7f7ab5e660a3fa48358a86c684 100644
--- a/ops/kubernetes/nomad-prod-util.yaml
+++ b/ops/kubernetes/nomad-prod-util.yaml
@@ -6,10 +6,6 @@ nomad:
       usesBetaData: false
       officialUrl: "https://nomad-lab.eu/prod/v1/gui"
 
-    proxy:
-      external:
-        path: "/prod/v1/util"
-
     gui:
       debug: true
 
@@ -26,22 +22,24 @@ nomad:
     north:
       enabled: false
 
+    archive:
+      use_new_writer: true
+
   image:
     tag: "prod"
 
-  ingress:
-    hosts:
-      - host: nomad-lab.eu
-        paths:
-          - path: /prod/v1/util/
-            pathType: ImplementationSpecific
+  proxy:
+    path: "/prod/v1/util"
 
   app:
     replicaCount: 1
+    resources:
+      limits:
+        memory: "8Gi"
 
   worker:
-    replicaCount: 1
-    processes: 4
+    replicaCount: 2
+    processes: 8
     resources:
       limits:
         memory: "256Gi"
diff --git a/ops/kubernetes/nomad-prod.yaml b/ops/kubernetes/nomad-prod.yaml
index 50b5bce2630a54b862c977ffdbf544c31741cb45..4bfa0a6853080f091309acf1b86daeb45c17a3ca 100644
--- a/ops/kubernetes/nomad-prod.yaml
+++ b/ops/kubernetes/nomad-prod.yaml
@@ -1,9 +1,5 @@
 nomad:
   config:
-    proxy:
-      external:
-        path: "/prod/v1"
-
     dbname: nomad_prod_v1
 
     uploadurl: "https://nomad-lab.eu/prod/v1/api/uploads"
@@ -20,19 +16,13 @@ nomad:
   image:
     tag: "prod"
 
+  proxy:
+    path: "/prod/v1"
+
   ingress:
     annotations:
       nginx.ingress.kubernetes.io/limit-rps: "25"
       nginx.ingress.kubernetes.io/denylist-source-range: "141.35.40.36/32, 141.35.40.52/32"
-    hosts:
-      - host: cloud.nomad-lab.eu
-        paths:
-          - path: /prod/v1/
-            pathType: ImplementationSpecific
-      - host: nomad-lab.eu
-        paths:
-          - path: /prod/v1/
-            pathType: ImplementationSpecific
 
   app:
     replicaCount: 18
diff --git a/ops/kubernetes/nomad/templates/NOTES.txt b/ops/kubernetes/nomad/templates/NOTES.txt
index ec8177584e0a6f66444c2b5f05b1705933db8fff..b53ec5b87e23b5481c81dabe86916a2adeda8ae9 100644
--- a/ops/kubernetes/nomad/templates/NOTES.txt
+++ b/ops/kubernetes/nomad/templates/NOTES.txt
@@ -1,20 +1,19 @@
+{{- $path := .Values.nomad.proxy.path -}}
 1. Get the application URL by running these commands:
 {{- if .Values.nomad.ingress.enabled }}
 {{- range $host := .Values.nomad.ingress.hosts }}
-  {{- range .paths }}
-  http{{ if $.Values.nomad.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
-  {{- end }}
+  http{{ if $.Values.nomad.ingress.tls }}s{{ end }}://{{ $host }}{{ $path }}
 {{- end }}
-{{- else if contains "NodePort" .Values.nomad.proxy.service.type }}
+{{- else if contains "NodePort" .Values.nomad.service.type }}
   export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "nomad.fullname" . }})
   export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
   echo http://$NODE_IP:$NODE_PORT
-{{- else if contains "LoadBalancer" .Values.nomad.proxy.service.type }}
+{{- else if contains "LoadBalancer" .Values.nomad.service.type }}
      NOTE: It may take a few minutes for the LoadBalancer IP to be available.
            You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "nomad.fullname" . }}'
   export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "nomad.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
-  echo http://$SERVICE_IP:{{ .Values.nomad.proxy.service.port }}
-{{- else if contains "ClusterIP" .Values.nomad.proxy.service.type }}
+  echo http://$SERVICE_IP:{{ .Values.nomad.service.port }}
+{{- else if contains "ClusterIP" .Values.nomad.service.type }}
   export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "nomad.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
   export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
   echo "Visit http://127.0.0.1:8080 to use your application"
diff --git a/ops/kubernetes/nomad/templates/app/deployment.yaml b/ops/kubernetes/nomad/templates/app/deployment.yaml
index f02c6287f6a39a377bfd6ef7e805da6883153bbf..42c6f154c40fbd04860276db854770bc74ec34c2 100644
--- a/ops/kubernetes/nomad/templates/app/deployment.yaml
+++ b/ops/kubernetes/nomad/templates/app/deployment.yaml
@@ -56,18 +56,18 @@ spec:
               protocol: TCP
           livenessProbe:
             httpGet:
-              path: "{{ .Values.nomad.config.proxy.external.path }}/alive"
+              path: "{{ .Values.nomad.proxy.path }}/alive"
               port: 8000
-            initialDelaySeconds: 60
-            periodSeconds: 30
-            timeoutSeconds: 5
+            initialDelaySeconds: 90
+            periodSeconds: 10
+            timeoutSeconds: {{ add .Values.nomad.proxy.timeout 10}}
           readinessProbe:
             httpGet:
-              path: "{{ .Values.nomad.config.proxy.external.path }}/alive"
+              path: "{{ .Values.nomad.proxy.path }}/alive"
               port: 8000
-            initialDelaySeconds: 60
-            periodSeconds: 15
-            timeoutSeconds: 5
+            initialDelaySeconds: 90
+            periodSeconds: 3
+            timeoutSeconds: {{ add .Values.nomad.proxy.timeout 3 }}
           {{- with .Values.nomad.app.resources }}
           resources:
             {{- . | toYaml | nindent 12 }}
diff --git a/ops/kubernetes/nomad/templates/configmap.yml b/ops/kubernetes/nomad/templates/configmap.yml
index f1983e7e6fb83ed0aad93eac06b57894a12ff7a5..68df7ef8572f51c3f91fe1c2a6c95941a3fd1fb6 100644
--- a/ops/kubernetes/nomad/templates/configmap.yml
+++ b/ops/kubernetes/nomad/templates/configmap.yml
@@ -46,11 +46,11 @@ data:
       host: "{{ .Values.nomad.config.logstash.host }}"
       tcp_port: {{ .Values.nomad.config.logstash.port }}
     services:
-      api_host: "{{ .Values.nomad.config.proxy.external.host }}"
-      api_port: {{ .Values.nomad.config.proxy.external.port }}
-      api_base_path: "{{ .Values.nomad.config.proxy.external.path }}"
+      api_host: "{{ index .Values.nomad.ingress.hosts 0 }}"
+      api_port: {{ .Values.nomad.service.port }}
+      api_base_path: "{{ .Values.nomad.proxy.path }}"
       api_secret: "{{ .Values.nomad.config.api.secret }}"
-      https: {{ .Values.nomad.config.proxy.external.https }}
+      https: true
       upload_limit: {{ .Values.nomad.config.api.uploadLimit }}
       admin_user_id: {{ .Values.nomad.config.keycloak.admin_user_id }}
       aitoolkit_enabled: {{ .Values.nomad.config.services.aitoolkit.enabled }}
@@ -120,16 +120,16 @@ data:
     {{ end }}
     north:
       enabled: {{ .Values.nomad.config.north.enabled }}
-      hub_host: "{{ .Values.nomad.config.proxy.external.host }}"
-      hub_port: {{ .Values.nomad.config.proxy.external.port }}
+      hub_host: "{{ index .Values.nomad.ingress.hosts 0 }}"
+      hub_port: {{ .Values.nomad.service.port }}
       hub_service_api_token: "{{ .Values.nomad.config.north.hubServiceApiToken }}"
-    {{ if .Values.nomad.archive }}
-    archive: {{ .Values.nomad.archive | toYaml | nindent 6 }}
+    {{ if .Values.nomad.config.archive }}
+    archive: {{ .Values.nomad.config.archive | toYaml | nindent 6 }}
     {{ end }}
-    {{ if .Values.nomad.plugins }}
-    plugins: {{ .Values.nomad.plugins | toYaml | nindent 6 }}
+    {{ if .Values.nomad.config.plugins }}
+    plugins: {{ .Values.nomad.config.plugins | toYaml | nindent 6 }}
     {{ end }}
-    {{ if .Values.nomad.normalize }}
-    normalize: {{ .Values.nomad.normalize | toYaml | nindent 6 }}
+    {{ if .Values.nomad.config.normalize }}
+    normalize: {{ .Values.nomad.config.normalize | toYaml | nindent 6 }}
     {{ end }}
 {{- end }}
\ No newline at end of file
diff --git a/ops/kubernetes/nomad/templates/ingress-api.yaml b/ops/kubernetes/nomad/templates/ingress-api.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..5766492abc33a0de33ee5ee089c50393c4663d8f
--- /dev/null
+++ b/ops/kubernetes/nomad/templates/ingress-api.yaml
@@ -0,0 +1,63 @@
+{{- if .Values.nomad.ingress.enabled -}}
+{{- $fullName := include "nomad.fullname" . -}}
+{{- $svcPort := .Values.nomad.service.port -}}
+{{- $path := .Values.nomad.proxy.path -}}
+{{- if and .Values.nomad.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
+  {{- if not (hasKey .Values.nomad.ingress.annotations "kubernetes.io/ingress.class") }}
+  {{- $_ := set .Values.nomad.ingress.annotations "kubernetes.io/ingress.class" .Values.nomad.ingress.className}}
+  {{- end }}
+{{- end }}
+{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
+apiVersion: networking.k8s.io/v1
+{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
+apiVersion: networking.k8s.io/v1beta1
+{{- else -}}
+apiVersion: extensions/v1beta1
+{{- end }}
+kind: Ingress
+metadata:
+  name: {{ $fullName }}-api
+  labels:
+    {{- include "nomad.labels" . | nindent 4 }}
+  annotations:
+    nginx.ingress.kubernetes.io/proxy-request-buffering: "off"
+    nginx.ingress.kubernetes.io/proxy-send-timeout: "{{ .Values.nomad.proxy.timeout }}"
+    nginx.ingress.kubernetes.io/proxy-read-timeout: "{{ .Values.nomad.proxy.timeout }}"
+    nginx.ingress.kubernetes.io/proxy-connect-timeout: "{{ .Values.nomad.proxy.connectionTimeout }}"
+    nginx.ingress.kubernetes.io/limit-connections: "{{ .Values.nomad.ingress.limitConnectionsApi }}"
+    {{- with .Values.nomad.ingress.annotations }}
+    {{- toYaml . | nindent 4 }}
+    {{- end }}
+spec:
+  {{- if and .Values.nomad.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
+  ingressClassName: {{ .Values.nomad.ingress.className }}
+  {{- end }}
+  {{- if .Values.nomad.ingress.tls }}
+  tls:
+    {{- range .Values.nomad.ingress.tls }}
+    - hosts:
+        {{- range .hosts }}
+        - {{ . | quote }}
+        {{- end }}
+      secretName: {{ .secretName }}
+    {{- end }}
+  {{- end }}
+  rules:
+    {{- range .Values.nomad.ingress.hosts }}
+    - host: {{ . | quote }}
+      http:
+        paths:
+          - path: {{ trimSuffix "/" $path }}/api
+            pathType: ImplementationSpecific
+            backend:
+              {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
+              service:
+                name: {{ $fullName }}-proxy
+                port:
+                  number: {{ $svcPort }}
+              {{- else }}
+              serviceName: {{ $fullName }}-proxy
+              servicePort: {{ $svcPort }}
+              {{- end }}
+    {{- end }}
+{{- end }}
diff --git a/ops/kubernetes/nomad/templates/ingress.yaml b/ops/kubernetes/nomad/templates/ingress.yaml
index 9bb37fa0e0c5e3d568fc7de29c5e068144bff5f6..bd4fa2ac99384cb3d1fc4516821d8754b56a9b97 100644
--- a/ops/kubernetes/nomad/templates/ingress.yaml
+++ b/ops/kubernetes/nomad/templates/ingress.yaml
@@ -1,6 +1,7 @@
 {{- if .Values.nomad.ingress.enabled -}}
 {{- $fullName := include "nomad.fullname" . -}}
 {{- $svcPort := .Values.nomad.service.port -}}
+{{- $path := .Values.nomad.proxy.path -}}
 {{- if and .Values.nomad.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
   {{- if not (hasKey .Values.nomad.ingress.annotations "kubernetes.io/ingress.class") }}
   {{- $_ := set .Values.nomad.ingress.annotations "kubernetes.io/ingress.class" .Values.nomad.ingress.className}}
@@ -18,10 +19,15 @@ metadata:
   name: {{ $fullName }}
   labels:
     {{- include "nomad.labels" . | nindent 4 }}
-  {{- with .Values.nomad.ingress.annotations }}
   annotations:
+    nginx.ingress.kubernetes.io/proxy-request-buffering: "off"
+    nginx.ingress.kubernetes.io/proxy-send-timeout: "{{ .Values.nomad.proxy.timeout }}"
+    nginx.ingress.kubernetes.io/proxy-read-timeout: "{{ .Values.nomad.proxy.timeout }}"
+    nginx.ingress.kubernetes.io/proxy-connect-timeout: "{{ .Values.nomad.proxy.connectionTimeout }}"
+    nginx.ingress.kubernetes.io/limit-connections: "{{ .Values.nomad.ingress.limitConnections }}"
+    {{- with .Values.nomad.ingress.annotations }}
     {{- toYaml . | nindent 4 }}
-  {{- end }}
+    {{- end }}
 spec:
   {{- if and .Values.nomad.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
   ingressClassName: {{ .Values.nomad.ingress.className }}
@@ -38,14 +44,11 @@ spec:
   {{- end }}
   rules:
     {{- range .Values.nomad.ingress.hosts }}
-    - host: {{ .host | quote }}
+    - host: {{ . | quote }}
       http:
         paths:
-          {{- range .paths }}
-          - path: {{ .path }}
-            {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }}
-            pathType: {{ .pathType }}
-            {{- end }}
+          - path: {{ $path }}
+            pathType: ImplementationSpecific
             backend:
               {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
               service:
@@ -56,6 +59,5 @@ spec:
               serviceName: {{ $fullName }}-proxy
               servicePort: {{ $svcPort }}
               {{- end }}
-          {{- end }}
     {{- end }}
 {{- end }}
diff --git a/ops/kubernetes/nomad/templates/proxy/configmap.yml b/ops/kubernetes/nomad/templates/proxy/configmap.yml
index c1992939edb5748ee6d46f127b8d9c70dfdcd73a..81ce9788023eb3bbb2a3392a8159f1abaab0d674 100644
--- a/ops/kubernetes/nomad/templates/proxy/configmap.yml
+++ b/ops/kubernetes/nomad/templates/proxy/configmap.yml
@@ -23,8 +23,8 @@ data:
       server_name   www.example.com;
       proxy_set_header Host $host;
 
-      proxy_connect_timeout {{ .Values.nomad.config.proxy.timeout }};
-      proxy_read_timeout {{ .Values.nomad.config.proxy.timeout }};
+      proxy_connect_timeout {{ .Values.nomad.proxy.connectionTimeout }};
+      proxy_read_timeout {{ .Values.nomad.proxy.timeout }};
       proxy_pass_request_headers      on;
       underscores_in_headers          on;
 
@@ -50,29 +50,29 @@ data:
         proxy_pass http://{{ include "nomad.fullname" . }}-app:8000;
       }
 
-      location ~ {{ .Values.nomad.config.proxy.external.path }}\/?(gui)?$ {
-        rewrite ^ {{ .Values.nomad.config.proxy.external.path }}/gui/ permanent;
+      location ~ {{ .Values.nomad.proxy.path }}\/?(gui)?$ {
+        rewrite ^ {{ .Values.nomad.proxy.path }}/gui/ permanent;
       }
 
-      location {{ .Values.nomad.config.proxy.external.path }}/gui/ {
+      location {{ .Values.nomad.proxy.path }}/gui/ {
         proxy_intercept_errors on;
         error_page 404 = @redirect_to_index;
         proxy_pass http://{{ include "nomad.fullname" . }}-app:8000;
       }
 
       location @redirect_to_index {
-        rewrite ^ {{ .Values.nomad.config.proxy.external.path }}/gui/index.html break;
+        rewrite ^ {{ .Values.nomad.proxy.path }}/gui/index.html break;
         proxy_pass http://{{ include "nomad.fullname" . }}-app:8000;
       }
 
-      location {{ .Values.nomad.config.proxy.external.path }}/docs/ {
+      location {{ .Values.nomad.proxy.path }}/docs/ {
         proxy_intercept_errors on;
         error_page 404 = @redirect_to_index_docs;
         proxy_pass http://{{ include "nomad.fullname" . }}-app:8000;
       }
 
       location @redirect_to_index_docs {
-        rewrite ^ {{ .Values.nomad.config.proxy.external.path }}/docs/index.html break;
+        rewrite ^ {{ .Values.nomad.proxy.path }}/docs/index.html break;
         proxy_pass http://{{ include "nomad.fullname" . }}-app:8000;
       }
 
@@ -98,12 +98,12 @@ data:
 
       location ~ /api/v1/entries/edit {
         proxy_buffering off;
-        proxy_read_timeout {{ .Values.nomad.config.proxy.editTimeout }};
+        proxy_read_timeout {{ .Values.nomad.proxy.editTimeout }};
         proxy_pass http://{{ include "nomad.fullname" . }}-app:8000;
       }
 
       {{- if .Values.nomad.config.north.enabled }}
-      location {{ .Values.nomad.config.proxy.external.path }}/north/ {
+      location {{ .Values.nomad.proxy.path }}/north/ {
           client_max_body_size 500m;
           proxy_pass http://{{ include "jupyterhub.fullname" . }}-proxy-public;
 
@@ -120,6 +120,5 @@ data:
           proxy_buffering off;
       }
       {{- end }}
-
     }
 {{- end}}
\ No newline at end of file
diff --git a/ops/kubernetes/nomad/templates/proxy/deployment.yaml b/ops/kubernetes/nomad/templates/proxy/deployment.yaml
index bcbebd6045238e1b6236ef0072bc13cd2a863c54..ee1f7526f061dd50e785d83e5c3989fb7c795155 100644
--- a/ops/kubernetes/nomad/templates/proxy/deployment.yaml
+++ b/ops/kubernetes/nomad/templates/proxy/deployment.yaml
@@ -60,16 +60,18 @@ spec:
               protocol: TCP
           livenessProbe:
             httpGet:
-              path: "{{ .Values.nomad.config.proxy.external.path }}/gui/index.html"
+              path: "{{ .Values.nomad.proxy.path }}/gui/index.html"
               port: http
-            initialDelaySeconds: 60
-            periodSeconds: 15
+            initialDelaySeconds: 90
+            periodSeconds: 10
+            timeoutSeconds: {{ add .Values.nomad.proxy.timeout 10 }}
           readinessProbe:
             httpGet:
-              path: "{{ .Values.nomad.config.proxy.external.path }}/gui/index.html"
+              path: "{{ .Values.nomad.proxy.path }}/gui/index.html"
               port: http
-            initialDelaySeconds: 60
+            initialDelaySeconds: 90
             periodSeconds: 3
+            timeoutSeconds: {{ add .Values.nomad.proxy.timeout 3}}
           {{- with .Values.nomad.proxy.resources }}
           resources:
             {{- . | toYaml | nindent 12 }}
diff --git a/ops/kubernetes/nomad/templates/worker/deployment.yaml b/ops/kubernetes/nomad/templates/worker/deployment.yaml
index 10a987d189034d2591f018500df657a374f5956b..71e8a2c20f338ff64ba8c8ca25ea1d98d8b69258 100644
--- a/ops/kubernetes/nomad/templates/worker/deployment.yaml
+++ b/ops/kubernetes/nomad/templates/worker/deployment.yaml
@@ -103,29 +103,23 @@ spec:
             {{- end }}
           command: ["python", "-m", "celery", "-A", "nomad.processing", "worker", "-n", "$(NOMAD_CELERY_NODE_NAME)" {{ if .Values.nomad.worker.processes }}, "-c", "{{ .Values.nomad.worker.processes }}"{{ end }}{{ if .Values.nomad.worker.maxTasksPerChild }}, "--max-tasks-per-child", "{{ .Values.nomad.worker.maxTasksPerChild }}"{{ end }}]
           livenessProbe:
-            # httpGet:
-            #   path: /
-            #   port: http
             exec:
               command:
               - bash
               - -c
               - NOMAD_LOGSTASH_LEVEL=WARNING python -m celery -A nomad.processing status | grep "${NOMAD_CELERY_NODE_NAME}:.*OK"
             initialDelaySeconds: 30
-            periodSeconds: 30
-            timeoutSeconds: 30
+            periodSeconds: 120
+            timeoutSeconds: 60
           readinessProbe:
-            # httpGet:
-            #   path: /
-            #   port: http
             exec:
               command:
               - bash
               - -c
               - NOMAD_LOGSTASH_LEVEL=WARNING python -m celery -A nomad.processing status | grep "${NOMAD_CELERY_NODE_NAME}:.*OK"
-            initialDelaySeconds: 15
-            periodSeconds: 30
-            timeoutSeconds: 30
+            initialDelaySeconds: 30
+            periodSeconds: 120
+            timeoutSeconds: 60
       volumes:
       {{- with .Values.nomad.volumes }}
         {{- toYaml . | nindent 8 }}
diff --git a/ops/kubernetes/nomad/values.yaml b/ops/kubernetes/nomad/values.yaml
index ccd1e0b213c386104e2ea8160b0854aeb9efca96..ac2cba4fee1aa8045b283998904535d7a5fad996 100644
--- a/ops/kubernetes/nomad/values.yaml
+++ b/ops/kubernetes/nomad/values.yaml
@@ -148,31 +148,17 @@ nomad:
       ## automatically gz based on header
       gzip: true
 
-    proxy:
-      # Set a nodePort to create a NodePort service instead of ClusterIP. Also set a nodeIP for the externalIP.
-      timeout: 120
-      editTimeout: 1800
-      external:
-        host: "nomad-lab.eu"
-        port: 80
-        path: "/fairdi/nomad/latest"
-        https: true
-
-
   ingress:
     enabled: false
+    limitConnections: 32
+    limitConnectionsApi: 8
+    hosts:
+      - nomad-lab.eu
     className: ""
     annotations:
       nginx.ingress.kubernetes.io/ssl-redirect: "false"
-    hosts:
-      - host: chart-example.local
-        paths:
-          - path: /
-            pathType: ImplementationSpecific
+      nginx.ingress.kubernetes.io/proxy-body-size: "32g"
     tls: []
-    #  - secretName: chart-example-tls
-    #    hosts:
-    #      - chart-example.local
 
   # Additional volumes on the output Deployment definition.
   volumes: []
@@ -196,6 +182,11 @@ nomad:
   ## Everything concerning the nginx that serves the gui, proxies the api
   #  It is run via NodePort service
   proxy:
+    path: "/fairdi/nomad/latest"
+    timeout: 60
+    editTimeout: 60
+    connectionTimeout: 10
+
     replicaCount: 1
     # Set a nodePort to create a NodePort service instead of ClusterIP. Also set a nodeIP for the externalIP.
 
diff --git a/ops/kubernetes/values.yaml b/ops/kubernetes/values.yaml
index b16203492d192e4c75a142b36f3786fb24e45bc6..42bb25c5d6bde49c937d58f5497039b30810c3aa 100644
--- a/ops/kubernetes/values.yaml
+++ b/ops/kubernetes/values.yaml
@@ -11,11 +11,6 @@ nomad:
       isBeta: false
       usesBetaData: false
 
-    proxy:
-      external:
-        host: "nomad-lab.eu"
-        path: "/prod/v1"
-
     gui:
       debug: false
       encyclopediaBase: "https://nomad-lab.eu/prod/rae/encyclopedia/#"
@@ -84,34 +79,25 @@ nomad:
 
   ingress:
     enabled: true
+    limitConnections: 32
+    limitConnectionsApi: 8
     className: "nginx"
     annotations:
       cert-manager.io/cluster-issuer: "letsencrypt-production"
-      nginx.ingress.kubernetes.io/proxy-body-size: "32g"
-      nginx.ingress.kubernetes.io/proxy-request-buffering: "off"
-      nginx.ingress.kubernetes.io/proxy-connect-timeout: "10"
-      nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
-      nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
-      nginx.ingress.kubernetes.io/limit-rps: "25"
+      nginx.ingress.kubernetes.io/limit-rps: "32"
       nginx.ingress.kubernetes.io/denylist-source-range: "141.35.40.36/32, 141.35.40.52/32"
     hosts:
-      - host: cloud.nomad-lab.eu
-        paths:
-          - path: /prod/v1/
-            pathType: ImplementationSpecific
-      - host: nomad-lab.eu
-        paths:
-          - path: /prod/v1/
-            pathType: ImplementationSpecific
+      - nomad-lab.eu
     tls:
-      - secretName: cloud-nomad-lab-eu-tls
-        hosts:
-          - cloud.nomad-lab.eu
       - secretName: nomad-lab-eu-tls
         hosts:
           - nomad-lab.eu
 
   proxy:
+    timeout: 60
+    editTimeout: 60
+    host: "nomad-lab.eu"
+    path: "/prod/v1"
     nodeSelector:
       environment: prod
       "nomad-lab.eu/app": ""
diff --git a/pyproject.toml b/pyproject.toml
index c3084d18867ac7f6db40ed3c2627a4c834340661..d3dcfb2c8131bf9f77bb7bfe8a1317588c34bcf1 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -106,7 +106,6 @@ infrastructure = [
     'dockerspawner==12.1.0',
     'oauthenticator==15.1.0',
     'validators==0.18.2',
-    'joblib>=1.1.0',
     'gunicorn>=21.2.0,<22.0.0',
     'importlib-metadata~=4.13.0' # Needed because of https://github.com/python/importlib_metadata/issues/411
 ]
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 1127633b87cba713389724f9401b4a59b532840f..5be4fda0fcb64af24d4e7d85f8d85aa0f8fd1cf6 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -36,7 +36,7 @@ bs4==0.0.1                # via -r requirements.txt, nomad-lab, nomad-lab (pypro
 build==0.9.0              # via nomad-lab (pyproject.toml), pip-tools
 cachetools==4.2.4         # via -r requirements.txt, nomad-lab, nomad-lab (pyproject.toml)
 cattrs==22.2.0            # via -r requirements.txt, requests-cache
-celery[redis]==5.2.7      # via celery, nomad-lab, nomad-lab (pyproject.toml)
+celery[redis]==5.2.7      # via -r requirements.txt, nomad-lab, nomad-lab (pyproject.toml)
 certifi==2022.12.7        # via -r requirements.txt, elasticsearch, httpcore, httpx, requests
 certipy==0.1.3            # via -r requirements.txt, jupyterhub
 cffi==1.15.1              # via -r requirements.txt, bcrypt, cryptography
@@ -53,7 +53,7 @@ commonmark==0.9.1         # via -r requirements.txt, recommonmark
 coverage==6.5.0           # via pytest-cov
 cryptography==39.0.0      # via -r requirements.txt, nomad-lab, nomad-lab (pyproject.toml), pyjwt, pyopenssl, rfc3161ng, secretstorage
 cycler==0.11.0            # via -r requirements.txt, matplotlib
-dask[array]==2022.2.0     # via -r requirements.txt, dask, hyperspy, kikuchipy, orix, pyxem
+dask[array]==2022.2.0     # via -r requirements.txt, hyperspy, kikuchipy, orix, pyxem
 debugpy==1.6.5            # via -r requirements.txt, ipykernel
 decorator==5.1.1          # via -r requirements.txt, ipyparallel, ipython, validators
 devtools==0.8.0           # via nomad-lab (pyproject.toml)
@@ -95,7 +95,7 @@ griddataformats==0.7.0    # via -r requirements.txt, mdanalysis
 gsd==2.7.0                # via -r requirements.txt, mdanalysis
 gunicorn==21.2.0          # via -r requirements.txt, nomad-lab, nomad-lab (pyproject.toml)
 h11==0.14.0               # via -r requirements.txt, httpcore, uvicorn
-h5grove[fastapi]==1.3.0   # via h5grove, nomad-lab, nomad-lab (pyproject.toml)
+h5grove[fastapi]==1.3.0   # via -r requirements.txt, nomad-lab, nomad-lab (pyproject.toml)
 h5py==3.6.0               # via -r requirements.txt, h5grove, hyperspy, ifes-apt-tc-data-modeling, kikuchipy, nionswift, nomad-lab, nomad-lab (pyproject.toml), orix, phonopy, pyfai, silx
 hjson==3.0.2              # via -r requirements.txt, nomad-lab, nomad-lab (pyproject.toml)
 html5lib==1.1             # via -r requirements.txt, nomad-lab, nomad-lab (pyproject.toml)
@@ -122,9 +122,9 @@ jedi==0.18.2              # via -r requirements.txt, ipython
 jeepney==0.8.0            # via keyring, secretstorage
 jinja2==3.0.3             # via -r requirements.txt, flask, hyperspy, jupyterhub, mkdocs, mkdocs-macros-plugin, mkdocs-material, sphinx
 jmespath==0.10.0          # via -r requirements.txt, nomad-lab, nomad-lab (pyproject.toml)
-joblib==1.1.0             # via -r requirements.txt, mdanalysis, nomad-lab, nomad-lab (pyproject.toml), pymatgen, scikit-learn
+joblib==1.1.0             # via -r requirements.txt, mdanalysis, nomad-lab, pymatgen, scikit-learn
 jsonpointer==2.3          # via -r requirements.txt, jsonschema
-jsonschema[format]==4.17.3  # via jsonschema, jupyter-telemetry, nomad-lab, nomad-lab (pyproject.toml), oauthenticator
+jsonschema[format]==4.17.3  # via -r requirements.txt, jupyter-telemetry, nomad-lab, nomad-lab (pyproject.toml), oauthenticator
 jupyter-client==7.4.8     # via -r requirements.txt, ipykernel, ipyparallel
 jupyter-core==4.12.0      # via -r requirements.txt, jupyter-client
 jupyter-telemetry==0.1.0  # via -r requirements.txt, jupyterhub
@@ -194,7 +194,7 @@ numpy-quaternion==2022.4.3  # via -r requirements.txt, orix
 oauthenticator==15.1.0    # via -r requirements.txt, nomad-lab, nomad-lab (pyproject.toml)
 oauthlib==3.2.2           # via -r requirements.txt, jupyterhub
 openpyxl==3.1.2           # via -r requirements.txt, nomad-lab, nomad-lab (pyproject.toml)
-optimade[mongo]==0.22.1   # via nomad-lab, nomad-lab (pyproject.toml), optimade
+optimade[mongo]==0.22.1   # via -r requirements.txt, nomad-lab, nomad-lab (pyproject.toml)
 orix==0.11.1              # via -r requirements.txt, diffsims, kikuchipy, pyxem
 orjson==3.9.4             # via -r requirements.txt, h5grove, nomad-lab, nomad-lab (pyproject.toml)
 packaging==24.0           # via -r requirements.txt, build, dask, docker, gunicorn, hyperspy, ipykernel, matplotlib, mdanalysis, mkdocs, mongomock, pint, pooch, pytest, scikit-image, sphinx
@@ -234,7 +234,7 @@ pycparser==2.21           # via -r requirements.txt, cffi
 pydantic==1.10.9          # via -r requirements.txt, fastapi, nomad-lab, nomad-lab (pyproject.toml), optimade
 pyfai==2023.9.0           # via -r requirements.txt, pyxem
 pygments==2.14.0          # via -r requirements.txt, ipython, mkdocs-material, readme-renderer, sphinx
-pyjwt[crypto]==2.6.0      # via nomad-lab, nomad-lab (pyproject.toml), pyjwt
+pyjwt[crypto]==2.6.0      # via -r requirements.txt, nomad-lab, nomad-lab (pyproject.toml)
 pylint==2.13.9            # via nomad-lab (pyproject.toml), pylint-mongoengine, pylint-plugin-utils
 pylint-mongoengine==0.4.0  # via nomad-lab (pyproject.toml)
 pylint-plugin-utils==0.7  # via nomad-lab (pyproject.toml), pylint-mongoengine
@@ -277,7 +277,7 @@ requests-cache==1.0.1     # via -r requirements.txt
 requests-toolbelt==0.10.1  # via python-gitlab, twine
 rfc3161ng==2.1.3          # via -r requirements.txt, nomad-lab, nomad-lab (pyproject.toml)
 rfc3339-validator==0.1.4  # via -r requirements.txt, jsonschema
-rfc3986[idna2008]==1.5.0  # via -r requirements.txt, httpx, rfc3986, twine
+rfc3986[idna2008]==1.5.0  # via -r requirements.txt, httpx, twine
 rfc3987==1.3.8            # via -r requirements.txt, jsonschema
 rope==0.21.0              # via nomad-lab (pyproject.toml)
 rsa==4.9                  # via -r requirements.txt, python-jose
@@ -335,7 +335,7 @@ unidecode==1.3.2          # via -r requirements.txt, nomad-lab, nomad-lab (pypro
 uri-template==1.2.0       # via -r requirements.txt, jsonschema
 url-normalize==1.4.3      # via -r requirements.txt, requests-cache
 urllib3==1.26.14          # via -r requirements.txt, docker, elasticsearch, pybis, requests, requests-cache
-uvicorn[standard]==0.20.0  # via h5grove, nomad-lab, nomad-lab (pyproject.toml), uvicorn
+uvicorn[standard]==0.20.0  # via -r requirements.txt, h5grove, nomad-lab, nomad-lab (pyproject.toml)
 uvloop==0.17.0            # via -r requirements.txt, uvicorn
 validators==0.18.2        # via -r requirements.txt, nomad-lab, nomad-lab (pyproject.toml)
 vine==5.0.0               # via -r requirements.txt, amqp, celery, kombu
diff --git a/requirements.txt b/requirements.txt
index 9490fca55fa745ff9a51b1d2011689386bddaeaa..6761ecddc37b3039890e3d087c4c86394929537c 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -30,7 +30,7 @@ bitarray==2.3.5           # via nomad-lab, nomad-lab (pyproject.toml), nomad_dos
 bs4==0.0.1                # via nomad-lab, nomad-lab (pyproject.toml)
 cachetools==4.2.4         # via nomad-lab, nomad-lab (pyproject.toml)
 cattrs==22.2.0            # via requests-cache
-celery[redis]==5.2.7      # via celery, nomad-lab, nomad-lab (pyproject.toml)
+celery[redis]==5.2.7      # via nomad-lab, nomad-lab (pyproject.toml)
 certifi==2022.12.7        # via elasticsearch, httpcore, httpx, requests
 certipy==0.1.3            # via jupyterhub
 cffi==1.15.1              # via bcrypt, cryptography
@@ -45,7 +45,7 @@ comm==0.1.4               # via ipywidgets
 commonmark==0.9.1         # via recommonmark
 cryptography==39.0.0      # via nomad-lab, nomad-lab (pyproject.toml), pyjwt, pyopenssl, rfc3161ng
 cycler==0.11.0            # via matplotlib
-dask[array]==2022.2.0     # via dask, hyperspy, kikuchipy, orix, pyxem
+dask[array]==2022.2.0     # via hyperspy, kikuchipy, orix, pyxem
 debugpy==1.6.5            # via ipykernel
 decorator==5.1.1          # via ipyparallel, ipython, validators
 diffpy-structure==3.1.0   # via diffsims, kikuchipy, orix
@@ -82,7 +82,7 @@ griddataformats==0.7.0    # via mdanalysis
 gsd==2.7.0                # via mdanalysis
 gunicorn==21.2.0          # via nomad-lab, nomad-lab (pyproject.toml)
 h11==0.14.0               # via httpcore, uvicorn
-h5grove[fastapi]==1.3.0   # via h5grove, nomad-lab, nomad-lab (pyproject.toml)
+h5grove[fastapi]==1.3.0   # via nomad-lab, nomad-lab (pyproject.toml)
 h5py==3.6.0               # via electronicparsers (dependencies/parsers/electronic/pyproject.toml), h5grove, hyperspy, ifes-apt-tc-data-modeling, kikuchipy, nionswift, nomad-lab, nomad-lab (pyproject.toml), orix, phonopy, pyfai, silx
 hjson==3.0.2              # via nomad-lab, nomad-lab (pyproject.toml)
 html5lib==1.1             # via nomad-lab, nomad-lab (pyproject.toml)
@@ -106,9 +106,9 @@ itsdangerous==2.1.2       # via flask, nomad-lab, nomad-lab (pyproject.toml)
 jedi==0.18.2              # via ipython
 jinja2==3.0.3             # via flask, hyperspy, jupyterhub, sphinx
 jmespath==0.10.0          # via nomad-lab, nomad-lab (pyproject.toml)
-joblib==1.1.0             # via mdanalysis, nomad-lab, nomad-lab (pyproject.toml), pymatgen, scikit-learn
+joblib==1.1.0             # via mdanalysis, nomad-lab, pymatgen, scikit-learn
 jsonpointer==2.3          # via jsonschema
-jsonschema[format]==4.17.3  # via jsonschema, jupyter-telemetry, nomad-lab, nomad-lab (pyproject.toml), oauthenticator
+jsonschema[format]==4.17.3  # via jupyter-telemetry, nomad-lab, nomad-lab (pyproject.toml), oauthenticator
 jupyter-client==7.4.8     # via ipykernel, ipyparallel
 jupyter-core==4.12.0      # via jupyter-client
 jupyter-telemetry==0.1.0  # via jupyterhub
@@ -150,7 +150,7 @@ nionswift==0.16.8         # via pynxtools (dependencies/parsers/nexus/pyproject.
 nionswift-io==0.15.1      # via nionswift
 nionui==0.6.11            # via nionswift
 nionutils==0.4.8          # via niondata, nionswift, nionswift-io, nionui
-nomad-lab @ git+https://github.com/nomad-coe/nomad.git@develop  # via nomad-lab, nomad-schema-plugin-run, nomad-schema-plugin-simulation-workflow, simulationparsers, workflowparsers (dependencies/parsers/workflow/pyproject.toml)
+nomad-lab @ git+https://github.com/nomad-coe/nomad.git@develop  # via atomisticparsers (dependencies/parsers/atomistic/pyproject.toml), nomad-schema-plugin-run, nomad-schema-plugin-simulation-workflow, simulationparsers, workflowparsers (dependencies/parsers/workflow/pyproject.toml)
 nomad-schema-plugin-run @ git+https://github.com/nomad-coe/nomad-schema-plugin-run.git@develop  # via atomisticparsers (dependencies/parsers/atomistic/pyproject.toml), nomad-schema-plugin-simulation-workflow, simulationparsers, workflowparsers (dependencies/parsers/workflow/pyproject.toml)
 nomad-schema-plugin-simulation-workflow @ git+https://github.com/nomad-coe/nomad-schema-plugin-simulation-workflow.git@develop  # via atomisticparsers (dependencies/parsers/atomistic/pyproject.toml), workflowparsers (dependencies/parsers/workflow/pyproject.toml)
 nptyping==1.4.4           # via nomad-lab, nomad-lab (pyproject.toml)
@@ -162,7 +162,7 @@ numpy-quaternion==2022.4.3  # via orix
 oauthenticator==15.1.0    # via nomad-lab, nomad-lab (pyproject.toml)
 oauthlib==3.2.2           # via jupyterhub
 openpyxl==3.1.2           # via nomad-lab, nomad-lab (pyproject.toml)
-optimade[mongo]==0.22.1   # via nomad-lab, nomad-lab (pyproject.toml), optimade
+optimade[mongo]==0.22.1   # via nomad-lab, nomad-lab (pyproject.toml)
 orix==0.11.1              # via diffsims, kikuchipy, pyxem
 orjson==3.9.4             # via h5grove, nomad-lab, nomad-lab (pyproject.toml)
 packaging==24.0           # via dask, docker, gunicorn, hyperspy, ipykernel, matplotlib, mdanalysis, mongomock, pint, pooch, scikit-image, sphinx
@@ -196,7 +196,7 @@ pycparser==2.21           # via cffi
 pydantic==1.10.9          # via fastapi, nomad-lab, nomad-lab (pyproject.toml), optimade
 pyfai==2023.9.0           # via pyxem
 pygments==2.14.0          # via ipython, sphinx
-pyjwt[crypto]==2.6.0      # via nomad-lab, nomad-lab (pyproject.toml), pyjwt
+pyjwt[crypto]==2.6.0      # via nomad-lab, nomad-lab (pyproject.toml)
 pymatgen==2023.9.25       # via asr, nomad-lab, nomad-lab (pyproject.toml)
 pymongo==4.3.3            # via mongoengine, nomad-lab, nomad-lab (pyproject.toml), optimade
 pyopenssl==23.0.0         # via certipy
@@ -226,7 +226,7 @@ requests==2.28.2          # via docker, eelsdbconverter (dependencies/parsers/ee
 requests-cache==1.0.1     # via pynxtools (dependencies/parsers/nexus/pyproject.toml)
 rfc3161ng==2.1.3          # via nomad-lab, nomad-lab (pyproject.toml)
 rfc3339-validator==0.1.4  # via jsonschema
-rfc3986[idna2008]==1.5.0  # via httpx, rfc3986
+rfc3986[idna2008]==1.5.0  # via httpx
 rfc3987==1.3.8            # via jsonschema
 rsa==4.9                  # via python-jose
 ruamel-yaml==0.17.21      # via jupyter-telemetry, oauthenticator, pymatgen