tokio_php follows security best practices for production deployments.
The server runs as unprivileged www-data user (UID/GID 82):
# Dockerfile
USER www-data
CMD ["tokio_php"]| Aspect | Description |
|---|---|
| Privilege separation | Process cannot modify system files |
| Container escape mitigation | Limited damage if container compromised |
| Kubernetes compatibility | Meets runAsNonRoot security context |
| Standard UID | UID 82 matches nginx/apache conventions |
# Check user
docker compose exec tokio_php whoami
# www-data
# Check process
docker compose exec tokio_php ps aux
# PID USER COMMAND
# 1 www-data tokio_php
# Check file ownership
docker compose exec tokio_php ls -la /var/www/html/
# drwxr-xr-x www-data www-data /var/www/htmlAll application files are owned by www-data:
# Create directory with correct ownership
RUN mkdir -p /var/www/html && chown -R www-data:www-data /var/www/html
# Copy files with correct ownership
COPY --chown=www-data:www-data www/ /var/www/html/| Path | Permission | Description |
|---|---|---|
/var/www/html |
755 |
Document root (read + execute) |
*.php |
644 |
PHP files (read only) |
uploads/ |
755 |
Upload directory (if needed) |
cache/ |
755 |
Cache directory (if needed) |
When enabled, preload script runs as www-data:
; Uncomment for frameworks (Laravel, Symfony)
opcache.preload=/var/www/html/preload.php
opcache.preload_user=www-dataThis ensures preloaded classes have correct ownership context. See OPcache & JIT for configuration details.
# Public interface (default)
LISTEN_ADDR=0.0.0.0:8080
# Localhost only (more secure)
LISTEN_ADDR=127.0.0.1:8080Keep metrics/health endpoints on separate port:
# Public traffic
LISTEN_ADDR=0.0.0.0:8080
# Internal only (not exposed to public)
INTERNAL_ADDR=127.0.0.1:9090Enable HTTPS for production:
TLS_CERT=/path/to/cert.pem
TLS_KEY=/path/to/key.pemFeatures:
- TLS 1.2 and TLS 1.3 support
- HTTP/2 via ALPN negotiation
- Strong cipher suites (rustls defaults)
Docker Compose uses secrets for secure certificate handling:
# docker-compose.yml
services:
app:
environment:
- TLS_CERT=/run/secrets/tls_cert
- TLS_KEY=/run/secrets/tls_key
secrets:
- tls_cert
- tls_key
secrets:
tls_cert:
file: ./certs/cert.pem
tls_key:
file: ./certs/key.pemBenefits:
- Files mounted in tmpfs (memory only)
- Not visible in
docker inspect - Mode 0444 by default (read-only)
Create TLS secret from certificate files:
kubectl create secret tls tokio-php-tls \
--cert=cert.pem \
--key=key.pemOr declaratively:
apiVersion: v1
kind: Secret
metadata:
name: tokio-php-tls
type: kubernetes.io/tls
data:
tls.crt: <base64-encoded-cert>
tls.key: <base64-encoded-key>Mount in deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: tokio-php
spec:
template:
spec:
containers:
- name: app
image: tokio-php:latest
env:
- name: TLS_CERT
value: /tls/tls.crt
- name: TLS_KEY
value: /tls/tls.key
volumeMounts:
- name: tls
mountPath: /tls
readOnly: true
volumes:
- name: tls
secret:
secretName: tokio-php-tls
defaultMode: 0400 # Owner read-onlyFor automatic certificate management with Let's Encrypt:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: tokio-php-cert
spec:
secretName: tokio-php-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- example.com
- www.example.comcert-manager automatically:
- Obtains certificates from Let's Encrypt
- Stores them in the specified Secret
- Renews before expiration (30 days by default)
Alternative: terminate TLS at Ingress level:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tokio-php
spec:
tls:
- hosts:
- example.com
secretName: tokio-php-tls
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: tokio-php
port:
number: 8080Considerations:
| Approach | Pros | Cons |
|---|---|---|
| App-level TLS | End-to-end encryption, HTTP/2 to app | More resource usage |
| Ingress TLS | Centralized certs, offload crypto | Internal traffic unencrypted |
For sensitive data, use app-level TLS or enable mTLS with service mesh (Istio, Linkerd).
Protect against abuse:
# 100 requests per minute per IP
RATE_LIMIT=100
RATE_WINDOW=60See Rate Limiting for details.
Recommended security context for Kubernetes:
apiVersion: apps/v1
kind: Deployment
metadata:
name: tokio-php
spec:
template:
spec:
securityContext:
runAsNonRoot: true
runAsUser: 82
runAsGroup: 82
fsGroup: 82
containers:
- name: app
image: tokio-php:latest
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
volumeMounts:
- name: tmp
mountPath: /tmp
- name: cache
mountPath: /var/cache
volumes:
- name: tmp
emptyDir: {}
- name: cache
emptyDir: {}| Setting | Value | Purpose |
|---|---|---|
runAsNonRoot |
true |
Enforce non-root |
runAsUser |
82 |
www-data UID |
allowPrivilegeEscalation |
false |
No sudo/setuid |
readOnlyRootFilesystem |
true |
Immutable container |
capabilities.drop |
ALL |
No Linux capabilities |
Mount application code as read-only:
volumes:
- ./www:/var/www/html:roPrevent resource exhaustion:
deploy:
resources:
limits:
cpus: '2'
memory: 1G
reservations:
cpus: '0.5'
memory: 256MUse Docker networks to isolate services:
networks:
frontend:
backend:
internal: true # No external access
services:
app:
networks:
- frontend
- backend
database:
networks:
- backend # Only accessible from apptokio_php handles HTTP parsing securely:
| Protection | Implementation |
|---|---|
| Header size limits | Hyper defaults (64KB) |
| Body size limits | Configurable in PHP |
| Path traversal | Resolved to document root |
| Request timeout | 5s header read timeout |
Always validate input in PHP:
<?php
// Validate and sanitize input
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
if ($id === false) {
http_response_code(400);
exit('Invalid ID');
}
// Use prepared statements for SQL
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([$id]);Access logs include client IP for audit:
ACCESS_LOG=1 docker compose up -dLog format includes:
- Client IP (
ip) - Request ID (
request_id) - User-Agent (
ua) - X-Forwarded-For (
xff)
See Configuration for details.
- Run as non-root (
USER www-data) - Enable TLS (
TLS_CERT,TLS_KEY) - Use secrets for certificates (not plain volumes)
- Enable rate limiting (
RATE_LIMIT) - Separate internal endpoints (
INTERNAL_ADDR) - Read-only volumes (
:ro) - Resource limits configured
- Access logging enabled (
ACCESS_LOG=1) - Disable debug logging (
LOG_LEVEL=warn)
-
runAsNonRoot: true -
allowPrivilegeEscalation: false -
readOnlyRootFilesystem: true -
capabilities.drop: ALL - TLS via
kubernetes.io/tlsSecret - Secret volume
defaultMode: 0400 - cert-manager for auto-renewal (if using Let's Encrypt)
- Network policies configured
- Pod security policy/standards enforced
If you discover a security vulnerability, please report it responsibly:
- Do not open a public issue
- Email security details privately
- Allow time for fix before disclosure
- Docker - Docker security, resource limits, read-only volumes
- Configuration - Environment variables reference
- HTTP/2 & TLS - TLS configuration details
- Rate Limiting - Abuse prevention
- Logging - Access logs and audit trail
- Graceful Shutdown - Zero-downtime deployments
- Architecture - System design overview