Python Uvicorn Logging SSL and Production Configuration
python uvicorn logging ssl and production configuration: Configure Uvicorn for production with SSL termination, structured logging, and worker settings. Learn how to s...
Configuring Uvicorn for production means making deliberate choices about SSL termination, logging, and process management. The combination of python uvicorn logging ssl and production configuration often appears together because each setting affects how the server behaves under real traffic.
Uvicorn's Default SSL and Logging Behavior
Uvicorn, the ASGI server, does not enable SSL by default. You must supply a certificate and private key. Similarly, logging is configured with a default formatter that prints access logs to stdout and error logs to stderr. In production, these defaults are rarely sufficient: you need to control log volume, format, and destination, and you need to terminate TLS with a valid certificate chain.
The primary way to configure these settings is through the uvicorn.run() call or the command-line interface. Both accept the same options, so you can choose whichever fits your deployment.
Enabling SSL with Certificates and Keys
To serve HTTPS, pass the certificate and key paths to Uvicorn. The certificate file should contain the server certificate and any intermediate certificates in the correct order.
import uvicorn uvicorn.run( "app.main:app", host="0.0.0.0", port=443, ssl_certfile="/etc/ssl/certs/server.crt", ssl_keyfile="/etc/ssl/private/server.key", )
The key file must be readable by the user running Uvicorn. If the key is encrypted, you can provide a passphrase via ssl_keyfile_password. For production, keep the key outside the repository and set restrictive file permissions.
You can also control the TLS version and cipher suite with ssl_version and ssl_ciphers. For example, to require TLS 1.2 or higher:
ssl_version=ssl.PROTOCOL_TLS_SERVER, ssl_ciphers="ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256"
This uses Python's ssl module constants, so import ssl in your script.
Configuring Logging for Production
Uvicorn has two loggers: uvicorn.access and uvicorn.error. The access logger records each HTTP request, while the error logger captures startup messages, errors, and internal exceptions. By default, both use a plain text formatter and print to stdout/stderr.
For production, you often want structured logs, such as JSON, so that log aggregation tools can parse them. Uvicorn accepts a logging configuration dictionary via log_config. Here is an example that sends access logs to stdout and error logs to stderr, both in JSON format:
import json import logging LOG_CONFIG = { "version": 1, "disable_existing_loggers": False, "formatters": { "json": { "()": "pythonjsonlogger.jsonlogger.JsonFormatter", "format": "%(asctime)s %(levelname)s %(name)s %(message)s", } }, "handlers": { "access": { "class": "logging.StreamHandler", "stream": "ext://sys.stdout", "formatter": "json", }, "error": { "class": "logging.StreamHandler", "stream": "ext://sys.stderr", "formatter": "json", }, }, "loggers": { "uvicorn.access": {"handlers": ["access"], "level": "INFO", "propagate": False}, "uvicorn.error": {"handlers": ["error"], "level": "INFO", "propagate": False}, n }, }
This configuration relies on the python-json-logger package. If you cannot add that dependency, you can write a custom formatter that outputs JSON using the standard library.
To apply this configuration, pass it to uvicorn.run:
uvicorn.run("app.main:app", log_config=LOG_CONFIG)
You can also specify a logging config file with the --log-config CLI flag, which accepts a YAML or JSON file.
Managing Access Logs and Request Details
The access log line includes the client IP, request method, path, status code, and response time. In production, you may want to disable access logs when they are too noisy, or you may want to enrich them with request IDs. Uvicorn does not natively support adding custom fields to access logs, but you can override the access log handler by subclassing uvicorn.logging.AccessFormatter or by wrapping the ASgi app.
A simpler approach is to keep Uvicorn's access log and rely on a reverse proxy for request correlation. If you run Uvicorn behind nginx or a load balancer, set --proxy-headers so that Uvicorn trusts the X-Forwarded-For header and logs the real client IP instead of the proxy IP.
uvicorn app.main:app --proxy-headers --forwarded-allow-ips="127.0.0.1"
The --forwarded-allow-ips list restricts which proxies are trusted. Only set this to the actual proxy IPs; otherwise, clients can spo their IP addresses.
Production Process and Worker Configuration
Uvicorn supports multiple worker processes with the --workers flag or the workers argument. Each worker runs a separate Python process, so they do not share memory. Use this when you have multiple CPU cores and your application is stateless or uses an external session store.
uvicorn.run("app.main:app", host="0.0.0.0", port=443, workers=4,, ssl_crtfile=..., ssl_keyfile=...)
When using workers, keep in mind that Uvicorn's --reload flag is intended for development and should not be used in production. Reloading in a multi-worker setup can cause inconsistent state and file descriptor leaks.
For graceful shutdown, set --timeout-ggraceful-shutdown to allow in-flight requests to complete before the worker exits. This is important when you deploy new versions and want zero-downtime restarts.
SSL Termination and Reverse Proxy Considerations
In many production deployments, SSL is terminated at a reverse proxy (nginx, HAProxy, or a cloud load balancer) rather than at Uvicorn itself. This reduces the TLS handshake overhead on the application server and centralizes certificate management. When you do that, Uvicorn should run in HTTP mode, and you must enable --proxy-headers so that it respects the X-Forwarded-Proto header to generate correct absolute URLs in redirects and link generation.
If you terminate SSL at Uvicorn directly, you are responsible for certificate renewal, key security, and TLS configuration. The tradeoff is that you avoid an extra network hop and have fewer moving parts. For a small service with a single instance, direct SSL termination is acceptable. For a horizontally scaled service, a reverse proxy is usually the better choice.
Common SSL and Logging Pitfalls
One frequent issue is that Uvicorn's access log does not include the TLS version or cipher used for a request. If you need that information for audit purposes, you must capture it in your application code via the ASGI scope. The scope dictionary contains server and client addresses, but not TLS details. To get the negotiated cipher,, you need to to access the underlying socket, which is not exposed through the standard ASGI interface. In practice, most teams rely on the reverse proxy to log TLS parameters.
Another pitfall is forgetting to set --forwarded-allow-ips when using --proxy-headers. Without it, Uvicorn only trusts X-Forwarded-For from localhost. If your proxy runs on a different host, the real client IP will be replaced by the proxy IP in logs, which makes debugging harder.
When you configure logging, ensure that the disable_existing_loggers setting is False; otherwise, Uvicorn's internal loggers may be silently disabled. Also, avoid setting the log level to DEBUG in production, as it can produce a large volume of output and slow down the server.