Plugin API Key Configuration#
Dify's plugin system uses two distinct shared-secret keys for inter-service authentication between the api/worker/agent_backend services and the plugin_daemon:
| Key pair | "Client" side variable | "Server" side variable | Direction |
|---|---|---|---|
| API β Daemon | PLUGIN_DAEMON_KEY | SERVER_KEY (plugin daemon) | api/worker calling plugin_daemon |
| Daemon β API | INNER_API_KEY_FOR_PLUGIN (api/worker) | PLUGIN_DIFY_INNER_API_KEY (daemon/agent) | plugin_daemon/agent_backend calling api |
The two keys#
PLUGIN_DAEMON_KEY β authenticates calls from the api and worker services to the plugin daemon.
- Python default:
"plugin-api-key" api/.env.exampleships the valuelYkiYYT6owG+71oLerGzA7GXCgOT++6ovaezWAjpCjf+Sjc3ZtU+qUEidocker/.env.examplesets the same literal as default for the Docker deployment- The compose file passes it to
plugin_daemonasSERVER_KEY
INNER_API_KEY_FOR_PLUGIN β authenticates callbacks from plugin_daemon and agent_backend into the api. This is the "inner API key" that the API server validates on incoming requests.
- Python default:
"inner-api-key" api/.env.exampleships the valueQaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1docker/.env.exampledefines it asPLUGIN_DIFY_INNER_API_KEY(daemon's variable name) with the same value- The compose file injects it into both
api/workerasINNER_API_KEY_FOR_PLUGINand intoplugin_daemonasDIFY_INNER_API_KEYβ both sourced from${PLUGIN_DIFY_INNER_API_KEY}
Synchronization in docker-compose.yaml#
The single PLUGIN_DIFY_INNER_API_KEY variable in docker/.env is the synchronization point. Compose maps it to the correct environment variable name for each service:
apiservice:INNER_API_KEY_FOR_PLUGIN: ${PLUGIN_DIFY_INNER_API_KEY:-QaHbTe77...}workerservice:INNER_API_KEY_FOR_PLUGIN: ${PLUGIN_DIFY_INNER_API_KEY:-QaHbTe77...}plugin_daemonservice:DIFY_INNER_API_KEY: ${PLUGIN_DIFY_INNER_API_KEY:-QaHbTe77...}agent_backendservice:DIFY_AGENT_INNER_API_KEY: ${DIFY_AGENT_INNER_API_KEY:-${PLUGIN_DIFY_INNER_API_KEY:-QaHbTe77...}}
Similarly, PLUGIN_DAEMON_KEY from docker/.env feeds both api/worker (via shared-api-worker-config env_file) and plugin_daemon (SERVER_KEY) and agent_backend (DIFY_AGENT_PLUGIN_DAEMON_API_KEY) .
Enforcement in the API#
The plugin_inner_api_only decorator in api/controllers/inner_api/wraps.py enforces this key:
- Returns
404ifPLUGIN_DAEMON_KEYis not set (guards the entire inner API surface). - Reads the
X-Inner-Api-Keyrequest header. - Returns
404if the header doesn't matchINNER_API_KEY_FOR_PLUGIN.
The agent_inner_api_only decorator is an alias for plugin_inner_api_only β the agent_backend shares the same credentials for its callbacks into the API.
Production security#
The hardcoded fallback values in compose (lYkiYYT6owG+... / QaHbTe77...) are insecure defaults. PR #34408 introduced CI tooling to detect and replace blank secrets, but the compose fallbacks still exist. For production:
- Set
PLUGIN_DAEMON_KEYandPLUGIN_DIFY_INNER_API_KEYto independent random strings (e.g.,openssl rand -base64 42) indocker/.env. - Both must be set to the same values at the same time across all affected services; a mismatch will cause
404responses on all plugin-initiated callbacks and inner API tool calls. AGENT_BACKEND_API_TOKEN/DIFY_AGENT_API_TOKEN(the token theapiuses to callagent_backend) is a separate secret and must also be rotated .
Quick reference β variable names per service#
| Service | Variable | Role |
|---|---|---|
api, worker | PLUGIN_DAEMON_KEY | Key used to call plugin_daemon |
api, worker | INNER_API_KEY_FOR_PLUGIN | Key expected in X-Inner-Api-Key header |
plugin_daemon | SERVER_KEY | Validates calls from api/worker |
plugin_daemon | DIFY_INNER_API_KEY | Sent as X-Inner-Api-Key when calling api |
agent_backend | DIFY_AGENT_PLUGIN_DAEMON_API_KEY | Calls plugin_daemon |
agent_backend | DIFY_AGENT_INNER_API_KEY | Sent as X-Inner-Api-Key when calling api |
docker/.env | PLUGIN_DAEMON_KEY | Source of truth for daemon key |
docker/.env | PLUGIN_DIFY_INNER_API_KEY | Source of truth for inner API key |