> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/tokio-rs/axum/llms.txt
> Use this file to discover all available pages before exploring further.

# Deployment

> Deploy Axum applications to production with best practices

Deploying Axum applications requires attention to performance, reliability, and security considerations. This guide covers production deployment strategies.

## Running with Hyper

Axum uses Hyper under the hood. The standard way to run an Axum app:

```rust theme={null}
use axum::Router;

#[tokio::main]
async fn main() {
    let app = Router::new();

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
        .await
        .unwrap();
    
    println!("Listening on {}", listener.local_addr().unwrap());
    axum::serve(listener, app).await;
}
```

<Info>
  Binding to `0.0.0.0` makes your server accessible from outside the host machine. Use `127.0.0.1` for local-only access.
</Info>

## Graceful shutdown

Handle shutdown signals gracefully to avoid dropping in-flight requests:

### Basic graceful shutdown

```rust theme={null}
use axum::Router;
use tokio::net::TcpListener;
use tokio::signal;

#[tokio::main]
async fn main() {
    let app = Router::new();
    let listener = TcpListener::bind("0.0.0.0:3000").await.unwrap();

    axum::serve(listener, app)
        .with_graceful_shutdown(shutdown_signal())
        .await;
}

async fn shutdown_signal() {
    let ctrl_c = async {
        signal::ctrl_c()
            .await
            .expect("failed to install Ctrl+C handler");
    };

    #[cfg(unix)]
    let terminate = async {
        signal::unix::signal(signal::unix::SignalKind::terminate())
            .expect("failed to install signal handler")
            .recv()
            .await;
    };

    #[cfg(not(unix))]
    let terminate = std::future::pending::<()>();

    tokio::select! {
        _ = ctrl_c => {},
        _ = terminate => {},
    }

    println!("Shutdown signal received, starting graceful shutdown");
}
```

<Steps>
  <Step title="Handle SIGTERM and SIGINT">
    Listen for shutdown signals from the operating system or container orchestrator.
  </Step>

  <Step title="Stop accepting new requests">
    The server stops accepting new connections but continues processing existing ones.
  </Step>

  <Step title="Wait for in-flight requests">
    Allow existing requests to complete normally.
  </Step>

  <Step title="Clean shutdown">
    Close all connections and exit gracefully.
  </Step>
</Steps>

### With timeout protection

Prevent requests from hanging during shutdown:

```rust theme={null}
use axum::{
    http::StatusCode,
    routing::get,
    Router,
};
use tower_http::timeout::TimeoutLayer;
use std::time::Duration;

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/slow", get(|| async {
            tokio::time::sleep(Duration::from_secs(5)).await;
            "Done"
        }))
        .layer(
            // Requests timeout after 10 seconds
            TimeoutLayer::with_status_code(
                StatusCode::REQUEST_TIMEOUT,
                Duration::from_secs(10),
            ),
        );

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
        .await
        .unwrap();

    axum::serve(listener, app)
        .with_graceful_shutdown(shutdown_signal())
        .await;
}
```

## Advanced serving with Hyper

For more control over the HTTP server, use Hyper's low-level API:

```rust theme={null}
use axum::{
    extract::Request,
    Router,
};
use hyper::body::Incoming;
use hyper_util::rt::{TokioExecutor, TokioIo};
use hyper_util::server;
use tokio::net::TcpListener;
use tower::Service;
use std::net::SocketAddr;

#[tokio::main]
async fn main() {
    let app = Router::new();
    let listener = TcpListener::bind("0.0.0.0:3000").await.unwrap();

    loop {
        let (socket, _remote_addr) = listener.accept().await.unwrap();
        let tower_service = app.clone();

        tokio::spawn(async move {
            let socket = TokioIo::new(socket);

            let hyper_service = hyper::service::service_fn(
                move |request: Request<Incoming>| {
                    tower_service.clone().call(request)
                }
            );

            if let Err(err) = server::conn::auto::Builder::new(TokioExecutor::new())
                .serve_connection_with_upgrades(socket, hyper_service)
                .await
            {
                eprintln!("Failed to serve connection: {err:#}");
            }
        });
    }
}
```

<Accordion title="When to use low-level Hyper API">
  * Custom connection handling logic
  * Per-connection state or middleware
  * Fine-grained control over HTTP/1.1 vs HTTP/2
  * Custom TLS configuration
  * Performance optimization for specific use cases
</Accordion>

## Configuration management

Use environment variables and configuration files:

```rust theme={null}
use axum::Router;
use serde::Deserialize;
use std::net::SocketAddr;

#[derive(Deserialize)]
struct Config {
    #[serde(default = "default_host")]
    host: String,
    #[serde(default = "default_port")]
    port: u16,
    #[serde(default = "default_workers")]
    workers: usize,
}

fn default_host() -> String {
    "0.0.0.0".to_string()
}

fn default_port() -> u16 {
    3000
}

fn default_workers() -> usize {
    num_cpus::get()
}

impl Config {
    fn from_env() -> Result<Self, config::ConfigError> {
        config::Config::builder()
            .add_source(config::Environment::default())
            .build()?
            .try_deserialize()
    }
}

#[tokio::main]
async fn main() {
    let config = Config::from_env().unwrap_or_else(|_| Config {
        host: default_host(),
        port: default_port(),
        workers: default_workers(),
    });

    let addr: SocketAddr = format!("{}:{}", config.host, config.port)
        .parse()
        .expect("Invalid address");

    let app = Router::new();
    let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
    
    println!("Server running on {addr}");
    axum::serve(listener, app).await;
}
```

## Logging and observability

Implement comprehensive logging for production:

<CodeGroup>
  ```rust Structured logging theme={null}
  use tracing_subscriber::{
      layer::SubscriberExt,
      util::SubscriberInitExt,
      EnvFilter,
  };

  #[tokio::main]
  async fn main() {
      tracing_subscriber::registry()
          .with(
              EnvFilter::try_from_default_env()
                  .unwrap_or_else(|_| "info,tower_http=debug".into()),
          )
          .with(tracing_subscriber::fmt::layer().json())
          .init();

      // Your app setup...
  }
  ```

  ```rust Request tracing theme={null}
  use tower_http::trace::{
      TraceLayer, DefaultMakeSpan, DefaultOnResponse,
  };
  use tower_http::LatencyUnit;
  use tracing::Level;

  let app = Router::new()
      .layer(
          TraceLayer::new_for_http()
              .make_span_with(
                  DefaultMakeSpan::new()
                      .include_headers(true)
                      .level(Level::INFO)
              )
              .on_response(
                  DefaultOnResponse::new()
                      .include_headers(true)
                      .latency_unit(LatencyUnit::Micros)
              )
      );
  ```

  ```rust Error tracking theme={null}
  use axum::{
      http::StatusCode,
      response::{IntoResponse, Response},
  };

  struct AppError(anyhow::Error);

  impl IntoResponse for AppError {
      fn into_response(self) -> Response {
          tracing::error!("Application error: {:?}", self.0);
          // Report to error tracking service (Sentry, etc.)
          (
              StatusCode::INTERNAL_SERVER_ERROR,
              format!("Internal server error"),
          ).into_response()
      }
  }

  impl<E> From<E> for AppError
  where
      E: Into<anyhow::Error>,
  {
      fn from(err: E) -> Self {
          Self(err.into())
      }
  }
  ```
</CodeGroup>

## Performance optimization

### Connection pooling

Use connection pools for databases:

```rust theme={null}
use sqlx::postgres::PgPoolOptions;
use std::time::Duration;

#[tokio::main]
async fn main() {
    let pool = PgPoolOptions::new()
        .max_connections(100)
        .acquire_timeout(Duration::from_secs(3))
        .connect("postgres://user:pass@localhost/db")
        .await
        .expect("Failed to create pool");

    let app = Router::new()
        .route("/users", get(list_users))
        .with_state(pool);

    // Server setup...
}
```

### Response compression

```rust theme={null}
use tower_http::compression::CompressionLayer;

let app = Router::new()
    .route("/api/data", get(large_response))
    .layer(CompressionLayer::new());
```

### Body size limits

```rust theme={null}
use tower_http::limit::RequestBodyLimitLayer;

let app = Router::new()
    .layer(RequestBodyLimitLayer::new(10 * 1024 * 1024)) // 10 MB
    .route("/upload", post(upload_handler));
```

## Docker deployment

Create an optimized Dockerfile:

```dockerfile theme={null}
# Build stage
FROM rust:1.75 as builder
WORKDIR /app

# Copy manifests
COPY Cargo.toml Cargo.lock ./

# Build dependencies (cached layer)
RUN mkdir src && echo "fn main() {}" > src/main.rs
RUN cargo build --release
RUN rm -rf src

# Copy source and build
COPY src ./src
RUN touch src/main.rs
RUN cargo build --release

# Runtime stage
FROM debian:bookworm-slim

# Install runtime dependencies
RUN apt-get update && apt-get install -y \
    ca-certificates \
    && rm -rf /var/lib/apt/lists/*

# Copy binary from builder
COPY --from=builder /app/target/release/myapp /usr/local/bin/myapp

# Create non-root user
RUN useradd -ms /bin/bash appuser
USER appuser

EXPOSE 3000

CMD ["myapp"]
```

<Info>
  Multi-stage builds keep your final image small by excluding build dependencies.
</Info>

## Kubernetes deployment

Example Kubernetes manifest:

```yaml theme={null}
apiVersion: apps/v1
kind: Deployment
metadata:
  name: axum-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: axum-app
  template:
    metadata:
      labels:
        app: axum-app
    spec:
      containers:
      - name: axum-app
        image: myregistry/axum-app:latest
        ports:
        - containerPort: 3000
        env:
        - name: HOST
          value: "0.0.0.0"
        - name: PORT
          value: "3000"
        - name: RUST_LOG
          value: "info"
        livenessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 5
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "512Mi"
            cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
  name: axum-app
spec:
  selector:
    app: axum-app
  ports:
  - port: 80
    targetPort: 3000
  type: LoadBalancer
```

## Health checks

Implement health and readiness endpoints:

```rust theme={null}
use axum::{
    extract::State,
    http::StatusCode,
    routing::get,
    Json, Router,
};
use serde_json::json;

#[derive(Clone)]
struct AppState {
    db_pool: sqlx::PgPool,
}

async fn health() -> StatusCode {
    StatusCode::OK
}

async fn readiness(State(state): State<AppState>) -> (StatusCode, Json<serde_json::Value>) {
    match sqlx::query("SELECT 1")
        .fetch_one(&state.db_pool)
        .await
    {
        Ok(_) => (StatusCode::OK, Json(json!({ "status": "ready" }))),
        Err(_) => (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(json!({ "status": "not ready" })),
        ),
    }
}

let app = Router::new()
    .route("/health", get(health))
    .route("/ready", get(readiness))
    .with_state(app_state);
```

## Security best practices

<Warning>
  Always validate and sanitize user input. Never trust data from external sources.
</Warning>

### HTTPS/TLS

For TLS termination, use a reverse proxy (nginx, Caddy) or cloud load balancer rather than handling TLS in your Axum app.

### Security headers

```rust theme={null}
use tower_http::set_header::SetResponseHeaderLayer;
use http::header;

let app = Router::new()
    .layer(SetResponseHeaderLayer::if_not_present(
        header::X_CONTENT_TYPE_OPTIONS,
        HeaderValue::from_static("nosniff"),
    ))
    .layer(SetResponseHeaderLayer::if_not_present(
        header::X_FRAME_OPTIONS,
        HeaderValue::from_static("DENY"),
    ));
```

### Rate limiting

```rust theme={null}
use tower_governor::{
    governor::GovernorConfigBuilder,
    GovernorLayer,
};

let governor_conf = Box::new(
    GovernorConfigBuilder::default()
        .per_second(10)
        .burst_size(20)
        .finish()
        .unwrap(),
);

let app = Router::new()
    .route("/api", get(handler))
    .layer(GovernorLayer { config: governor_conf });
```

## Monitoring and metrics

```rust theme={null}
use axum::routing::get;
use metrics_exporter_prometheus::{Matcher, PrometheusBuilder, PrometheusHandle};

fn setup_metrics() -> PrometheusHandle {
    PrometheusBuilder::new()
        .set_buckets_for_metric(
            Matcher::Full("http_request_duration_seconds".to_string()),
            &[0.001, 0.01, 0.1, 1.0, 10.0],
        )
        .unwrap()
        .install_recorder()
        .unwrap()
}

#[tokio::main]
async fn main() {
    let recorder_handle = setup_metrics();

    let app = Router::new()
        .route("/metrics", get(|| async move {
            recorder_handle.render()
        }));

    // Server setup...
}
```

<Tip>
  Monitor key metrics: request rate, error rate, response time (latency), and saturation (resource usage).
</Tip>
