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

# Sync certificates to a Linux server

> Deploy certificates to Linux servers over SSH.

Deploy certificates from Infisical to a directory on a Linux server over SSH. Files are written atomically over SFTP so a reader never sees a partially written certificate or key.

> [!NOTE]
>
> Certificate Syncs are configured per Application. First select which certificates to sync, then configure the Linux Server destination.

## Prerequisites

* An [SSH Connection](https://infisical.com/docs/integrations/app-connections/ssh.md) with access to the target server, or an [LDAP Connection](https://infisical.com/docs/integrations/app-connections/ldap.md) to the domain the server is joined to. See [using an LDAP connection for many servers](#using-an-ldap-connection-for-many-servers).
* For an LDAP Connection: the connection URL must use LDAPS on port 636, because a hardened domain controller rejects a plaintext simple bind on port 389 with `strongerAuthRequired`. The domain controller must be reachable as well as the target server, since every run reads the directory to derive the login.
* The destination directory must already exist on the server and the connection user must be able to write to it
* The server must be reachable from Infisical, either directly or through an [Infisical Gateway](https://infisical.com/docs/documentation/platform/gateways/overview.md)
* A Gateway is required to use a [health check](#health-check) or a [post-sync command](#post-sync-command), because the Gateway is what executes them. It must be running Infisical CLI v0.43.120 or later

## Create a Linux Server sync

> [tab] Infisical UI
>
> 1. In your Application, go to the **Certificate Syncs** tab and select **Create Sync**.
>
> 2. Select the **Linux Server** option.
>
> 3. Configure the **Destination**:
>    * **Connection**: The SSH Connection used to reach the server, or an LDAP Connection to the domain the server is joined to.
>    * **Destination Directory**: The absolute path to the directory where certificate files are written, for example `/etc/ssl/certs`.
>
> 4. Configure the **Target Host**. This step appears only when the connection is an LDAP Connection, because that connection reaches a whole domain rather than one machine:
>    * **Target Host**: The machine this sync delivers to. Servers found in the directory are offered as suggestions, and you can enter a host that isn't listed. Enter a host name or an IPv4 address. Underscores, ports and IPv6 literals aren't accepted.
>    * **Port**: Leave empty to use 22. Set a value only if the SSH listener is on a non-standard port.
>    * **Trusted Host Keys**: Optional. Paste the output of `ssh-keyscan <host>`. When set, the sync only connects if the host presents one of these keys.
>
> 5. Configure the **Sync Options**:
>    * **Export Format**: Choose **PEM** to write separate certificate, chain, and key files, **PKCS#12** to write a single password-protected `.pfx` bundle, or **Java KeyStore** to write a password-protected `.jks` keystore.
>    * **Certificate File Extension**: For PEM, choose `.pem` (default) or `.crt` for the certificate and chain files. Both hold the same PEM-encoded content, so pick the one the consuming service expects.
>    * **Combine Certificate and Chain**: For PEM, write the leaf certificate followed by the chain in a single file (a full-chain file, as nginx expects) and skip the separate chain file.
>    * **File Permissions** and **Private Key Permissions**: Octal modes for the delivered certificate and chain files (default `644`) and for the private key file (default `600`).
>    * **Owner** and **Group**: Optionally set the owner and group on the delivered files. Changing ownership requires the connection user to be root or to have passwordless sudo.
>    * **PKCS#12 Password** or **JKS Password**: Required when the export format is PKCS#12 or Java KeyStore. It protects the delivered bundle or keystore.
>    * **Keystore Alias**: For PKCS#12 and Java KeyStore, the alias of the private key entry, such as the value Tomcat expects in `certificateKeyAlias`. Leave it empty to use the base file name from the certificate name schema.
>    * **Include Truststore**: For Java KeyStore, also write a `.truststore.jks` file that holds the issuing CAs and the root CA as trusted certificates. It uses the same password as the keystore.
>    * **Include Private Key**: For PEM, controls whether the private key is written alongside the certificate. The sync fails for a certificate whose key is not available, for example one issued from an external CSR.
>    * **Include Root CA in Certificate Chain**: Include the root CA in the delivered chain. Leave it off when the consuming service only needs the intermediates.
>    * **Enable Removal of Certificates**: Delete the delivered files from the server when a certificate is removed from the sync, revoked, or expired.
>    * **Certificate Name Schema**: The base file name, using placeholders such as `{{commonName}}`, `{{certificateId}}`, or `{{shortCertificateId}}`. The export format adds the extension. A schema with no placeholder can be linked to only one certificate.
>    * **Auto-Sync Enabled**: Automatically sync certificates when changes occur.
>
> 6. Configure the **Commands**, or leave them empty:
>    * **Health check**: A command run on the server before any certificate is written, to confirm the host is ready, for example `test -w /etc/ssl/certs`. A non-zero exit stops the sync, so nothing is delivered. See [health check](#health-check).
>    * **Post-sync command**: A command run on the server after the sync delivers a certificate, so the service that uses it picks up the new file, for example `sudo systemctl reload nginx`. See [post-sync command](#post-sync-command).
>
> 7. Configure the **Details**:
>    * **Name**: The name of your sync.
>    * **Description**: Optional description.
>
> 8. Select which certificates should be synced.
>
> 9. Review and click **Create Sync**.

> [tab] API
>
> To create a **Linux Server Certificate Sync**, make an API request to the [Create Linux Server PKI Sync](https://infisical.com/docs/api-reference/endpoints/pki/syncs/linux-server/create.md) endpoint.
>
> `destinationConfig.host` is required when `connectionId` refers to an LDAP Connection, and an optional `port` sits alongside it, defaulting to 22. Both are rejected when `connectionId` refers to an SSH Connection, because that connection carries its own host and port.
>
> ### Sample request
>
> ```bash Request theme={"dark"}
> curl --request POST \
> --url https://app.infisical.com/api/v1/cert-manager/syncs/linux-server \
> --header 'Authorization: Bearer <access-token>' \
> --header 'Content-Type: application/json' \
> --data '{
>     "name": "my-linux-cert-sync",
>     "applicationId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
>     "connectionId": "550e8400-e29b-41d4-a716-446655440000",
>     "isAutoSyncEnabled": true,
>     "filters": {
>         "certificateOrderIds": ["550e8400-e29b-41d4-a716-446655440000"]
>     },
>     "syncOptions": {
>         "exportFormat": "pem",
>         "includePrivateKey": true,
>         "canRemoveCertificates": true,
>         "certificateNameSchema": "{{commonName}}",
>         "healthCheckCommand": "test -w /etc/ssl/certs",
>         "postSyncCommand": "sudo systemctl reload nginx"
>     },
>     "destinationConfig": {
>         "destinationPath": "/etc/ssl/certs",
>         "host": "server01.corp.example.com"
>     }
> }'
> ```

## Using an LDAP connection for many servers

An SSH Connection stores the target machine's address alongside its credential, so it reaches exactly one server. Pushing one certificate to fifty servers means fifty connections holding the same credential, and fifty objects to update when that credential is rotated.

An LDAP Connection removes that duplication. It's both the credential and the directory, so one connection serves as many syncs as you have servers. Each sync names its own **Target Host**, and every sync shares the one credential. When you rotate the service account's password on the connection, every sync that uses it picks up the new password on its next run with no further action.

This works because an Active Directory service account is a single identity. On a domain-joined Linux host, the same username and password that performs the LDAP bind performs the SSH logon, so no second credential is needed. The host must accept domain credentials over SSH, which is what a directory client such as SSSD provides.

The **Target Host** field offers machines read from the directory. The list isn't closed, because the name a domain controller advertises isn't always the address you can reach, so you can enter a host that isn't listed. The value must be a host name or an IPv4 address.

> [!NOTE]
>
> The sync authenticates to the Target Host with the connection's credential. Application Admins can set it, so give the service account only the access it needs to deliver certificates.

### The host must accept the `DOMAIN\user` login form

Infisical logs in to the server as `DOMAIN\user`, for example `CORP\svc-certs`. Whether a Linux host
accepts that form depends on how its directory client is configured.

A host joined to the domain with `realm join` uses the SSSD `ad` provider, which understands
`DOMAIN\user` out of the box. A host configured against the directory over LDAP instead uses the
`ldap` provider, whose default name parsing only understands `user@domain`, so the login is rejected
before the password is ever checked. Add the domain-qualified form to that host's `sssd.conf`:

```ini theme={"dark"}
[domain/CORP]
re_expression = (((?P<domain>[^\\]+)\\(?P<name>.+$))|((?P<name>[^@]+)@(?P<domain>.+$))|(^(?P<name>[^@\\]+)$))
use_fully_qualified_names = False
```

Confirm it with `getent passwd 'CORP\svc-certs'` on the server before creating the sync. If that
returns nothing, the sync will fail to authenticate no matter what the connection holds.

## Export formats

The file extension is set by the export format, not by the name schema. The name schema only provides the base file name.

**PEM** writes the certificate, chain, and private key (when included) as separate files. The certificate and chain files use the extension you select, either `.pem` (default) or `.crt`.

**PKCS#12** writes a single password-protected `.pfx` bundle that contains the certificate, chain, and private key.

**Java KeyStore** writes a password-protected `.jks` keystore for Java servers such as Tomcat, WebLogic, and JBoss. The keystore holds one private key entry with the certificate, chain, and private key, and the key uses the same password as the keystore. If you turn on **Include Truststore**, the sync also writes a `.truststore.jks` file with each issuing CA and the root CA as trusted certificates. The truststore includes the root CA even when **Include Root CA in Certificate Chain** is off, and it never includes the certificate itself.

Infisical writes a new keystore on every sync instead of adding to the file on the host. After a renewal, the keystore holds only the new certificate under the same alias, so old entries don't build up. Point your Java server at a keystore that only this sync writes to, because anything else in that file is replaced on the next sync.

> [!NOTE]
>
> Java KeyStore stores aliases in lowercase, so an alias of `Tomcat` is saved as `tomcat`. Java looks up aliases without regard to case, so configuring `Tomcat` in your server still finds the entry.

> [!WARNING]
>
> Java KeyStore isn't available when Infisical runs in FIPS mode, because the format can only protect private keys with cryptography that isn't FIPS-approved. Use PEM instead.

For a certificate whose common name is `app.example.com`, synced to `/etc/ssl/certs` with the default `{{commonName}}` name schema, the delivered files are:

```text theme={"dark"}
PEM (.pem extension)
  /etc/ssl/certs/app.example.com.pem        certificate
  /etc/ssl/certs/app.example.com.chain.pem  chain
  /etc/ssl/certs/app.example.com.key        private key (when included)

PKCS#12
  /etc/ssl/certs/app.example.com.pfx

Java KeyStore
  /etc/ssl/certs/app.example.com.jks             keystore
  /etc/ssl/certs/app.example.com.truststore.jks  truststore (when delivered)
```

## How it works

When syncing certificates, Infisical opens an SSH session to the server and, for each certificate, packages it in the chosen export format and writes the resulting files to the destination directory. Files are written atomically over SFTP so a reader never sees a partially written certificate or key.

## Commands

A sync can run two optional commands on the target server, in that server's own shell:

* A **health check** runs before any certificate is written and gates the delivery, for example `test -w /etc/ssl/certs`.
* A **post-sync command** runs after the delivery has finished, so the service that uses the certificate picks up the new file, for example `sudo systemctl reload nginx`.

Set each on the sync's **Commands** step. Leaving a field empty means nothing runs, so neither has a separate toggle. Both accept a whole script, so several steps can run in one go: put each on its own line, or chain them with `&&`.

> [!NOTE]
>
> Your [Infisical Gateway](https://infisical.com/docs/documentation/platform/gateways/overview.md) runs both commands, never Infisical. The sync's App Connection must therefore use a gateway, otherwise the command is rejected when you save the sync.
>
> The Gateway must be running Infisical CLI **v0.43.120 or later**. Earlier Gateways either can't run the command at all or can't unlock an SSH Connection's passphrase-protected private key, so the command fails. Run `infisical --version` on the Gateway host to check, and see [Gateway deployment](https://infisical.com/docs/documentation/platform/gateways/gateway-deployment.md) for how to upgrade.

### Available variables

Write a variable where you want this run's value. Infisical substitutes it before the command is sent to the server, so a command keeps working when the name schema or export format changes.

| Variable | Value |
| - | - |
| `{{certificatePath}}` | Full path of the certificate file this run writes. Single-certificate syncs only |
| `{{certificateDirectory}}` | The destination directory |
| `{{certificateFiles}}` | Every path this run writes, one per line |
| `{{commonName}}` | The certificate's common name. Single-certificate syncs only |
| `{{exportPassword}}` | The export password, set only when the export format is PKCS#12 or Java KeyStore. `{{pkcs12Password}}` is an older name for the same value and still works |

The paths are the same for both commands, but a health check runs before anything is written, so they describe the files the run is *about to* write rather than files already on the host.

Each value is inserted already quoted for the target's shell, so write `{{certificatePath}}` rather than `"{{certificatePath}}"`. A variable that has no value for the run becomes an empty string.

Only the names in this table are substituted. Anything else in double braces is left in the command exactly as you wrote it, so a command that calls a tool with its own braces (helm, jq, a Go template) keeps working, and a misspelled variable reaches the server as literal text rather than silently turning into nothing.

> [!NOTE]
>
> `{{certificatePath}}` and `{{commonName}}` name one specific certificate, so a command that uses either can only be saved on a sync with a single certificate linked. Linking a second certificate to such a sync is rejected, as is adding one of these variables to a sync that already has several. To act on every certificate in the run, use `{{certificateFiles}}`, which lists one path per line.

### Length limit

Each command is limited to 8192 characters, counted as you write it, before variables are substituted. For anything longer, put the logic in a script on the server and have the command call that script.

### Least privilege

Both commands run as the account the SSH Connection authenticates with, so constraining that account bounds what either can do. A service reload doesn't need root. Grant the connection user a `sudoers` rule for that one command instead:

```text theme={"dark"}
svc-infisical ALL=(root) NOPASSWD: /usr/bin/systemctl reload nginx
```

Only Application Admins can set either command, so treat that role as access to the target server.

> [!WARNING]
>
> The rendered command is visible in the target server's process table while it runs. This only matters if you reference `{{exportPassword}}`, and only to accounts that already have a shell on the server the sync writes private keys to.

### Health check

A sync can only tell you the host wasn't ready once it has already tried. If the destination directory was renamed or the service that owns it is stopped, you find out from a failed sync, and on a renewal that is the day the certificate was going to expire anyway.

A health check moves that discovery earlier. It runs on the target server before any certificate is written. If it exits non-zero, the sync stops there: no files are delivered, no removals happen, and no post-sync command runs.

The same check also runs on its own once a day, so a host that stops being ready between syncs is reported when it happens rather than at the next renewal.

Write a check that answers a question rather than one that changes something. `test -w /etc/ssl/certs` and `systemctl is-active nginx` are good checks. Creating the directory or starting the service is work for the post-sync command.

The check runs on the target server, not on Infisical, and gates everything after it:

```mermaid theme={"dark"}
sequenceDiagram
    participant I as Infisical
    participant G as Gateway
    participant S as Target server
    I->>G: Run the health check
    G->>S: Execute as the connection's account
    S-->>G: Output and outcome
    G-->>I: Ready, or the reason it is not
    Note over I: Not ready: the sync fails here and delivers nothing
    I->>G: Deliver certificate files
    G->>S: Write files
```

**1. Open the sync's Commands step**

Edit the sync and select **Commands**.

**2. Write the check**

Anything that exits non-zero when the host isn't ready works. The variables resolve to the files
this run is about to write, so a check can look at where a certificate is going:

```bash theme={"dark"}
set -e
test -w {{certificateDirectory}}
test ! -e {{certificatePath}} || test -w {{certificatePath}}
systemctl is-active --quiet nginx
```

To check every certificate the run would write instead of one, loop over `{{certificateFiles}}`:

```bash theme={"dark"}
echo {{certificateFiles}} | while read -r file; do test -w "$(dirname "$file")"; done
```

**3. Test it, then save the sync**

**Test check** runs the check against the host straight away, so you can see the result without waiting for a sync. It resolves variables against the certificates linked to the sync, so it works once at least one is selected and tells you if none is. `{{exportPassword}}` is the exception: the export password only exists once a saved sync delivers a certificate, so a test always rejects it.

Save the sync once the check passes. On a real run, a failing check fails the sync without delivering anything, and the reason appears as the sync's last error.

### Post-sync command

Delivering a certificate file doesn't make the service use it. NGINX keeps serving the old certificate until something reloads it. A post-sync command is that last step, run for you right after the files land.

It runs once per sync run that delivers at least one file. If a sync run doesn't complete, Infisical may attempt it again and the command runs with it, so prefer a command that is safe to run more than once.

The command runs on the target server, not on Infisical, and only after the whole delivery has finished:

```mermaid theme={"dark"}
sequenceDiagram
    participant I as Infisical
    participant G as Gateway
    participant S as Target server
    I->>G: Deliver certificate files
    G->>S: Write files
    G->>S: Remove files for certificates no longer synced
    Note over G,S: Only once the server is in its final state
    I->>G: Run the post-sync command
    G->>S: Execute as the connection's account
    S-->>G: Output and outcome
    G-->>I: Success, or the reason it failed
```

**1. Open the sync's Commands step**

Edit the sync and select **Commands**.

**2. Write the command**

Use a variable wherever you need a value from the run. Typing `{{` in the command field lists the ones available for this sync, so you can pick one instead of remembering the name:

```bash theme={"dark"}
cp {{certificatePath}} /etc/nginx/ssl/live.pem && systemctl reload nginx
```

**3. Save and trigger a sync**

Save the sync, then trigger it. If the command fails, the reason appears as the sync's last error.

### Command FAQ

> [accordion] When exactly does each command run?
>
> The health check runs before anything else in a sync run, including before Infisical opens the SFTP session it would write with. It also runs once a day on its own, whether or not a sync is due.
>
> The post-sync command runs after every file in the run has been delivered and after any removals, so the server is in its final state before the service reloads. A run that delivers nothing runs nothing. Renewals, adding a certificate, and a manual trigger all deliver files, so all three run the command. Removing a certificate from the sync does not.

> [accordion] What happens when a command fails?
>
> Either way the sync is marked failed and the reason appears as the sync's last error. A command that fails is reported, not repeated.
>
> What is left on the host differs. When the **health check** fails, every certificate in the run is reported as skipped with that same reason, so it is clear nothing reached the host, and files already on the server from an earlier run are left exactly as they are, because the sync never wrote or deleted anything. When the **post-sync command** fails, the delivered files stay in place, because they were written before the command ran.

> [accordion] Can I run several commands, and what happens if one of them fails?
>
> Yes, in either field. Write one per line, or chain them with `&&`.
>
> By default the shell only reports the **last** line's exit status, so an earlier line that fails is not noticed and the command counts as having succeeded. Start the script with `set -e` so the first failure stops it and fails the sync:
>
> ```bash theme={"dark"}
> set -e
> cp {{certificatePath}} /etc/nginx/ssl/live.pem
> nginx -t
> systemctl reload nginx
> ```

> [accordion] Is there a time limit?
>
> Yes, and it differs by command: 15 seconds for the health check, 30 seconds for the post-sync command. Both are enforced by the Gateway, and running out of time counts as a failure.
>
> The check's limit is deliberately much shorter, because the check gates delivery: a host that cannot answer a readiness question in fifteen seconds is itself the problem worth reporting. Keep the check to a question that is cheap to answer, and give a post-sync command that needs longer than thirty seconds a script on the server that starts the work in the background.

> [accordion] Can a command run twice for the same certificate?
>
> Yes, in some cases. If a sync run doesn't complete, Infisical may attempt it again, and both commands run as part of that attempt. Prefer commands that are safe to run more than once. A service reload or restart is.

> [accordion] Does a failed health check get retried?
>
> Not within the run. A host that isn't ready is usually not ready a few seconds later either, so retrying only delays the report. The next sync or the next daily check is the retry.

> [accordion] Where do I see the result of the daily health check?
>
> A failing daily check records the failure and its reason against the sync's health check, shown as Last Checked on the sync's page, and notifies the application's administrators. It doesn't change the sync's own status, which continues to report the last delivery. Either way the run is recorded in the sync's logs with the command and the first part of its output.

> [accordion] Can I use the health check to prepare the host instead of just checking it?
>
> You can, since it's a free-text command, but prefer not to. A check that also does work runs before every sync and has fifteen seconds to finish, and if it fails halfway you have changed the host and delivered nothing. Keep preparation in the post-sync command, or in a script the check only verifies the result of.

## Removing certificates

When certificate removal is enabled and a certificate is removed from the sync, revoked, or expired, Infisical deletes exactly the files it delivered for that certificate.

## FAQ

> [accordion] Can I import certificates from a Linux server back into Infisical?
>
> No. The Linux Server sync only delivers certificates to the server. It doesn't read certificates back into Infisical.

> [accordion] What happens if the destination directory does not exist?
>
> The sync fails with a clear error. Create the directory and grant the connection user write access, then run the sync again.

## What's next?

> [card] [Windows Server](https://infisical.com/docs/documentation/platform/pki/applications/certificate-syncs/windows-server.md)
>
> Deploy certificates to Windows servers over WinRM.

> [card] [Auto-Renewal](https://infisical.com/docs/documentation/platform/pki/applications/certificates.md#renewal)
>
> Enable automatic certificate renewal and syncing.

> [card] [Other Sync Destinations](https://infisical.com/docs/documentation/platform/pki/applications/certificate-syncs/overview)
>
> View all supported sync destinations.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.