# How to Secure Argo CD: A Complete Hardening Guide for Production Clusters

> Learn to secure Argo CD in production. This guide covers TLS hardening RBAC isolation and audit logging to boost your cluster security.

- Repository: [Argo Project/argo-cd](https://github.com/argoproj/argo-cd)
- Tags: how-to-guide
- Published: 2026-07-09

---

**Secure Argo CD by enforcing TLS 1.2, hardening RBAC policies, isolating the repo-server, disabling unused config tools, and enabling security audit logging with CWE identifiers.**

Argo CD is a GitOps continuous delivery platform that runs as a tightly coupled set of Kubernetes services within the `argoproj/argo-cd` repository. To secure Argo CD effectively, operators must address four pillars: authentication, authorization, transport-level protection, and data confidentiality. The following guide provides concrete configuration steps derived directly from the source code and official documentation to harden your deployment against credential leakage, unauthorized cluster modifications, and denial-of-service attacks.

## Authentication and Authorization Controls

Argo CD protects API access through **JSON Web Tokens (JWT)** and external identity providers. All API calls require a valid token issued either by the Argo CD server for the local `admin` account or through an integrated OIDC/Dex provider.

The JWT’s `groups` claim is matched against the **RBAC policy** defined in a ConfigMap. According to [`docs/operator-manual/rbac.md`](https://github.com/argoproj/argo-cd/blob/main/docs/operator-manual/rbac.md), rules grant or deny specific API verbs on resources based on these claims. While the default policy grants cluster-admin privileges, you should narrow this to the least-privilege set of namespaces and resources required for your GitOps workflows.

## Transport Layer Security (TLS) Configuration

All intra-service traffic between `argocd-server`, `argocd-repo-server`, and `argocd-application-controller` is encrypted via TLS. You can enforce TLS 1.2 minimum via the `argocd-cmd-params-cm` ConfigMap:

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-cmd-params-cm
  namespace: argocd
data:
  server.tls.minversion: "1.2"

```

Redis communication defaults to plain HTTP but can be secured using the `--redis-tls` flags passed to the server components, ensuring encrypted data-in-transit for cached application states.

## Repository Server Isolation and Hardening

The `argocd-repo-server` is designed to run without any Kubernetes privileges and never stores credentials long-term. It maintains only a read-only clone of allow-listed repositories, keeping the attack surface minimal.

Disable unused config-management tools to remove code-execution vectors. If you do not use Helm, disable it in `argocd-cmd-params-cm`:

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-cmd-params-cm
  namespace: argocd
data:
  repo.server.enable.helm: "false"
  repo.server.enable.kustomize: "true"

```

To prevent memory exhaustion attacks from maliciously large manifests, set the `reposerver.max.combined.directory.manifests.size` option. This caps the total size of raw JSON/YAML files an application may load, as documented in [`docs/operator-manual/security.md`](https://github.com/argoproj/argo-cd/blob/main/docs/operator-manual/security.md):

```yaml
data:
  reposerver.max.combined.directory.manifests.size: "5M"

```

## Cluster Privilege Minimization

By default, the Argo CD controller uses a cluster-admin role. You must edit `argocd-manager-role` and [`argocd-application-controller-role.yaml`](https://github.com/argoproj/argo-cd/blob/main/argocd-application-controller-role.yaml) to restrict write access to only your managed namespaces.

A minimal read-only ClusterRole configuration looks like this:

```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: argocd-manager-readonly
rules:
- apiGroups: [""]
  resources: ["pods", "services", "configmaps", "secrets"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
  resources: ["deployments", "statefulsets", "daemonsets"]
  verbs: ["get", "list", "watch"]

```

Apply this with `kubectl apply` and bind it to the `argocd-manager` ServiceAccount to reduce the blast radius of a compromised controller.

## Data Protection and Audit Logging

Sensitive data redaction is automatic. All API payloads and logs redact cluster credentials, Git tokens, OAuth2 client secrets, and Kubernetes Secret values. The admin password is stored as a **bcrypt hash** in [`manifests/base/config/argocd-secret.yaml`](https://github.com/argoproj/argo-cd/blob/main/manifests/base/config/argocd-secret.yaml):

```yaml
apiVersion: v1
kind: Secret
metadata:
  name: argocd-secret
  namespace: argocd
type: Opaque
data:
  admin.password: "<bcrypt-hash-base64>"
  admin.passwordMtime: "2024-07-09T00:00:00Z"

```

Rotate credentials regularly using the CLI:

```bash
argocd account update-password --account admin --current-password <old> --new-password <new>

```

Every API request is logged with a `security` field and optional CWE identifier for security-related events. Enable structured JSON logging for SIEM integration:

```yaml
data:
  server.loglevel: "debug"
  server.logformat: "json"

```

## Webhook and Network Hardening

Webhook payloads are treated as untrusted input. They trigger only a fast-track refresh of the affected application without executing payload content. Enforce rate-limiting on external webhook traffic at your ingress controller level, and restrict the `argocd-repo-server` ServiceAccount to read-only access for specific Git repositories.

## Step-by-Step Security Implementation

Combine these hardening measures into a single configuration. Your `argocd-cmd-params-cm` should include TLS enforcement, disabled tools, memory limits, and logging settings:

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-cmd-params-cm
  namespace: argocd
data:
  server.tls.minversion: "1.2"
  repo.server.enable.helm: "false"
  repo.server.enable.kustomize: "true"
  reposerver.max.combined.directory.manifests.size: "5M"
  server.loglevel: "debug"
  server.logformat: "json"

```

Apply this configuration with `kubectl apply -f <file>.yaml` and verify the changes by checking the `argocd-server` deployment logs for the loaded parameters.

## Summary

- **Enforce TLS 1.2** minimum on all Argo CD components via `argocd-cmd-params-cm` to prevent man-in-the-middle attacks.
- **Harden RBAC** by replacing the default cluster-admin role with minimal ClusterRoles that grant only `get`, `list`, and `watch` verbs where possible.
- **Isolate the repo-server** by disabling unused tools like Helm and setting `reposerver.max.combined.directory.manifests.size` to prevent memory DoS.
- **Protect credentials** by relying on bcrypt-hashed passwords in [`argocd-secret.yaml`](https://github.com/argoproj/argo-cd/blob/main/argocd-secret.yaml) and rotating the admin password regularly.
- **Enable audit logging** using JSON format and the `security` field to capture CWE-tagged events for SIEM analysis.

## Frequently Asked Questions

### How does Argo CD store the admin password securely?

Argo CD stores the admin password as a **bcrypt hash** in the `argocd-secret` resource located in [`manifests/base/config/argocd-secret.yaml`](https://github.com/argoproj/argo-cd/blob/main/manifests/base/config/argocd-secret.yaml). When you update the password via `argocd account update-password`, the CLI automatically generates the bcrypt hash and base64-encodes it for Kubernetes storage, ensuring plaintext credentials never persist in the cluster.

### Can I use OIDC instead of the built-in admin account?

Yes, Argo CD supports external OIDC providers and Dex for Single Sign-On (SSO). Configure your provider in the `argocd-cm` ConfigMap, and ensure you do not use `--insecure-skip-server-verification` in production. Tokens issued by OIDC providers expire according to the provider’s configuration and can be revoked through the provider’s console, offering superior control over the default static admin token.

### What is the default RBAC policy and why should I change it?

The default RBAC policy grants the application controller **cluster-admin** privileges via [`argocd-application-controller-role.yaml`](https://github.com/argoproj/argo-cd/blob/main/argocd-application-controller-role.yaml). This allows Argo CD to manage resources across all namespaces but violates the principle of least privilege. You should customize the ClusterRole to restrict write access (`create`, `update`, `delete`, `patch`) to only the specific namespaces and resource types managed by your GitOps workflows.

### How do I prevent memory exhaustion attacks on the repo-server?

Set the `reposerver.max.combined.directory.manifests.size` parameter in `argocd-cmd-params-cm` to limit the total uncompressed size of JSON/YAML manifests a single application can load into memory. This prevents malicious repositories from submitting oversized files that could crash the repo-server pod or exhaust node resources, effectively mitigating directory-based memory DoS attacks.