# How Caddy's Process Lifecycle Works: Start, Stop, and Signal Handling

> Understand Caddy's process lifecycle. Learn how Caddy starts, stops, and handles signals gracefully through configuration loading and app management. Explore the Caddy Go API.

- Repository: [Caddy/caddy](https://github.com/caddyserver/caddy)
- Tags: internals
- Published: 2026-03-03

---

**Caddy's process lifecycle is orchestrated through a deterministic sequence of configuration loading, context provisioning, and modular app management in [`caddy.go`](https://github.com/caddyserver/caddy/blob/main/caddy.go), supporting both graceful shutdowns via POSIX signals and programmatic control through the public Go API.**

Caddy is a modern, extensible web server written in Go that manages its process lifecycle through a tightly controlled sequence of initialization, configuration loading, and modular app orchestration. Understanding how Caddy starts up and shuts down is essential for operators running production workloads and developers embedding Caddy into custom Go binaries. This article examines the core mechanisms defined in the `caddyserver/caddy` repository that govern process initialization, graceful termination, and signal handling.

## The Boot Phase: CLI Entry Point

The lifecycle begins in [`cmd/main.go`](https://github.com/caddyserver/caddy/blob/main/cmd/main.go), which constructs the Cobra command tree and executes the selected subcommand. When you run `caddy run`, the binary enters the boot phase by parsing command-line flags and preparing the environment for configuration loading.

The entry point delegates to the `run` command implementation, which handles the transition from CLI arguments to the internal configuration system. This separation ensures that Caddy can be started equally from the command line, as a system service, or embedded as a library in another Go application.

## The Start Phase: Configuration and App Initialization

Once the boot phase completes, Caddy enters the start phase through a chain of functions in [`caddy.go`](https://github.com/caddyserver/caddy/blob/main/caddy.go) that transform raw configuration into running services.

### From Command Line to Run()

The `run` command invokes `caddy.Run(cfg)`, which marshals the provided `Config` struct to JSON and forwards it to `caddy.Load`. The `Load` function serves as the primary entry point for configuration changes, notifying the service manager and acquiring the global lock (`rawCfgMu`) to ensure thread safety.

`Load` delegates to `changeConfig`, which ultimately calls `unsyncedDecodeAndRun`. This function deserializes the JSON configuration and prepares the execution context.

### Provisioning and Starting Apps

The `provisionContext` function in [`caddy.go`](https://github.com/caddyserver/caddy/blob/main/caddy.go) creates a fresh `Context` instance that holds the active configuration, a cancellation function, and module-specific state. This context isolation allows Caddy to perform hot reloads by provisioning a new context while the old one remains active.

During provisioning, Caddy:
1. Initializes storage modules
2. Loads each **App** module defined in the configuration
3. Invokes `App.Start()` on every loaded application

The final `run` function iterates over the `apps` map inside the context and starts each application. Only after all apps report successful startup does Caddy consider the start phase complete and begin accepting traffic.

## The Stop Phase: Graceful Termination

Caddy supports multiple shutdown triggers, including the `caddy stop` command, API calls to `caddy.Stop()`, and POSIX signals. Regardless of the trigger, the stop phase follows a consistent path through [`caddy.go`](https://github.com/caddyserver/caddy/blob/main/caddy.go).

### Signal Handling and Triggers

On POSIX platforms, [`sigtrap_posix.go`](https://github.com/caddyserver/caddy/blob/main/sigtrap_posix.go) installs a goroutine (`trapSignalsPosix`) that listens for `SIGTERM`, `SIGQUIT`, and `SIGUSR1`. **SIGTERM** triggers `exitProcessFromSignal`, which invokes `exitProcess`—the same graceful shutdown routine used by the administrative API. **SIGQUIT** bypasses graceful shutdown and immediately calls `os.Exit(ExitCodeForceQuit)`.

**SIGUSR1** initiates a hot reload by re-reading the last-known configuration file and invoking the reload callback, allowing zero-downtime configuration updates without restarting the process.

### The Shutdown Sequence

When `caddy.Stop()` is called, it acquires the necessary locks and delegates to `unsyncedStop`. This function iterates over every started **App** in the current context and invokes `App.Stop()`, giving each module an opportunity to release resources, close connections, and persist state.

After all apps return from their `Stop()` methods, Caddy clears the global context (`currentCtx`), resets any persisted state such as the autosave file, and releases the configuration locks. This idempotent design ensures that repeated calls to `Stop()` remain safe.

## Implementing the App Interface

Every Caddy component that participates in the lifecycle must implement the **App interface** defined in [`modules.go`](https://github.com/caddyserver/caddy/blob/main/modules.go). This interface requires two methods:

- `Start() error` – Called during the provisioning phase to initialize listeners, connect to databases, or begin background tasks
- `Stop() error` – Called during shutdown to perform cleanup

The core orchestration logic in [`caddy.go`](https://github.com/caddyserver/caddy/blob/main/caddy.go) remains agnostic to specific module implementations, interacting with all components solely through this interface. This abstraction enables the modular architecture where HTTP servers, TLS automation managers, and custom plugins all share the same lifecycle guarantees.

## Practical Examples

### Embedding Caddy Programmatically

You can embed Caddy directly into your own Go binary by constructing a configuration and calling `caddy.Run()`:

```go
package main

import (
	"context"
	"log"

	"github.com/caddyserver/caddy/v2"
)

func main() {
	// Build a minimal Config (e.g. a simple file server)
	cfg := &caddy.Config{
		AppsRaw: caddy.ModuleMap{
			"http": caddy.ModuleMap{
				"servers": caddy.ModuleMap{
					"srv0": caddy.ModuleMap{
						"listen": []string{":8080"},
						"routes": []any{
							caddy.ModuleMap{
								"handle": []any{
									caddy.ModuleMap{
										"handler": "static_response",
										"body":    "Hello from embedded Caddy!",
									},
								},
							},
						},
					},
				},
			},
		},
	}

	// Run the configuration (starts apps)
	if err := caddy.Run(cfg); err != nil {
		log.Fatalf("Caddy failed to start: %v", err)
	}
	// Block until the process receives a termination signal
	<-context.Background().Done()
}

```

### Graceful Shutdown via API

To programmatically trigger a shutdown from within your application:

```go
package main

import (
	"log"

	"github.com/caddyserver/caddy/v2"
)

func main() {
	// ... assume Caddy is already running ...

	if err := caddy.Stop(); err != nil {
		log.Printf("error stopping Caddy: %v", err)
	}
}

```

This invokes the same `unsyncedStop` routine used by POSIX signal handlers, ensuring consistent behavior whether stopped via code or system signals.

## Summary

- **Boot phase**: [`cmd/main.go`](https://github.com/caddyserver/caddy/blob/main/cmd/main.go) initializes the CLI and delegates to the `run` subcommand, which transitions into the core lifecycle logic.
- **Start phase**: `caddy.Run()` → `Load()` → `provisionContext()` creates isolated execution contexts and starts each App via `App.Start()`.
- **Stop phase**: `caddy.Stop()` → `unsyncedStop()` iterates all Apps and invokes `App.Stop()`, protected by `rawCfgMu` and `currentCtxMu` locks.
- **Signal handling**: [`sigtrap_posix.go`](https://github.com/caddyserver/caddy/blob/main/sigtrap_posix.go) maps **SIGTERM** to graceful shutdown and **SIGUSR1** to configuration reload, while **SIGQUIT** forces immediate termination.
- **App interface**: Modular components implement `Start()` and `Stop()` methods, allowing the core to manage diverse functionality through a unified lifecycle contract.

## Frequently Asked Questions

### What signals does Caddy respond to for shutdown?

Caddy listens for **SIGTERM** and **SIGQUIT** on POSIX systems through the `trapSignalsPosix` function in [`sigtrap_posix.go`](https://github.com/caddyserver/caddy/blob/main/sigtrap_posix.go). SIGTERM triggers a graceful shutdown via `exitProcess`, allowing apps to clean up resources, while SIGQUIT forces immediate termination with `os.Exit(ExitCodeForceQuit)`. On non-POSIX platforms like Windows, [`sigtrap_nonposix.go`](https://github.com/caddyserver/caddy/blob/main/sigtrap_nonposix.go) provides stub implementations.

### How does Caddy handle configuration reloads without downtime?

When Caddy receives **SIGUSR1** or an API call to reload, it invokes `Load` with the new configuration while the existing context remains active. The `provisionContext` function creates a fresh `Context` with the updated config, starts the new apps, and only then swaps the global context pointer protected by `currentCtxMu`. This atomic hand-off ensures zero-downtime reloads.

### Can I embed Caddy in my own Go application?

Yes, Caddy is designed as an embeddable library. Import `github.com/caddyserver/caddy/v2`, construct a `caddy.Config` struct (or load from JSON), and call `caddy.Run(cfg)`. To stop the embedded server, call `caddy.Stop()`. This API bypasses the CLI entirely, giving you programmatic control over the process lifecycle within your own binary.

### What is the difference between graceful and forced shutdown?

Graceful shutdown, triggered by `caddy.Stop()` or SIGTERM, executes `unsyncedStop` which calls `App.Stop()` on every running module, allowing proper cleanup. Forced shutdown via SIGQUIT or `caddy stop --force` skips the app cleanup loop and exits immediately. The forced path does not invoke `unsyncedStop`, making it suitable for situations where the process is unresponsive but potentially risky for data consistency.