SFTP and FTP
On this page
The sftp and ftp adapters upload the build to a folder on a server: a VPS, shared hosting, or anything with an SSH or FTP login. Prefer sftp; use ftp when the host offers nothing else.
import { defineConfig } from "@kilim-dev/core";
import { sftp } from "@kilim-dev/core/adapters";
export default defineConfig({
url: "https://www.example.com",
deploy: sftp({
host: "example.com",
user: "deploy",
path: "/var/www/site",
hostKey: "SHA256:uNiVztksCsDhcc0u9e8BujQXVUpKZIDTMczCvj3tD2s",
}),
});pathis the site's folder on the server, the one the web server serves (oftenpublic_htmlor/var/www/<site>). An absolute path starts with/; anything else is relative to the folder the user signs in to. Missing folders are created.portdefaults to 22 for SFTP and 21 for FTP.- Every deploy writes
_kilim/deploy.jsonlast, the list of the site's files and their SHA-256 hashes, and the nextkilim deployuploads only new and changed files. When the folder has none (the first deploy, or files copied there another way), every file already on the server counts as changed and is uploaded again. - Old files are kept by default, because the folder often holds files that are not the site's:
.htaccess,.well-known/, another app.kilim deploysays how many it keeps. SetdeleteRemoved: trueto delete the files of earlier deploys that the build no longer has, and the folders they leave empty. Without a manifest that is every file inpaththat the build does not have, so look at the list fromkilim deploy --dry-runfirst. - Pages are uploaded after stylesheets and images, so a visitor never gets a page whose assets are missing.
_headersand_redirectsare not uploaded; Apache and nginx do not read them. Redirects fromaliasesandredirects.txtstill work through the redirect pages Kilim writes. For the security headers and CSP, set them in the server's config (or.htaccesson Apache).- Point the server's 404 page at
404.html:ErrorDocument 404 /404.htmlon Apache,error_page 404 /404.html;on nginx.
The user name is user in the config. Everything secret comes from environment variables, never the config:
| Variable | Meaning |
|---|---|
SFTP_PRIVATE_KEY | The private key itself (OpenSSH or PEM), e.g. from a CI secret. A key pasted on one line with \n for the line breaks works too. |
SFTP_PRIVATE_KEY_PATH | A key file instead, e.g. ~/.ssh/id_ed25519. Set this or SFTP_PRIVATE_KEY, not both. |
SFTP_PASSPHRASE | The key's passphrase, if it has one. |
SFTP_PASSWORD | A password, for servers without keys. |
SSH_AUTH_SOCK | A running ssh-agent; usually set already. On Windows, set it to \\.\pipe\openssh-ssh-agent to use the OpenSSH agent. |
Kilim always checks the server's key, as ssh does, so nobody can stand in for the server. The key has to be in ~/.ssh/known_hosts (connect once with ssh user@host and accept it), or its fingerprint in hostKey; a list of fingerprints is fine too. In CI there is usually no known_hosts, so set hostKey. Get the fingerprint from the host, or on the server with ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub. When the server's key is not known, kilim deploy stops and shows the fingerprint it got; check it before you copy it into the config.
import { ftp } from "@kilim-dev/core/adapters";
deploy: ftp({ host: "ftp.example.com", user: "me", path: "public_html" }),- The password comes from
FTP_PASSWORD. - FTP is encrypted with TLS (FTPS) by default:
secure: trueasks the server forAUTH TLSon port 21,secure: "implicit"uses TLS from the start on port 990. The certificate must be valid forhost, so use the host name the certificate is for (often the server's own name, not your domain). For a server with its own (self-signed) certificate, check the certificate with the host, save it as a PEM file and pointNODE_EXTRA_CA_CERTSat it, which Node.js reads at start. secure: falseis plain FTP, which sends the password and the files unencrypted. Kilim warns about it inkilim deployandkilim doctor; use it only when the host offers nothing else.- One file is sent at a time, as FTP allows one transfer per connection.
kilim doctor shows the address it connects to, which credentials it found, whether the server's key is in known_hosts or hostKey, and warns about plain FTP. It does not connect.
# .github/workflows/sftp.yml
name: SFTP
on:
push:
branches: [main]
permissions:
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
cache: npm
- run: npm ci
- run: npx kilim deploy
env:
SFTP_PRIVATE_KEY: ${{ secrets.SFTP_PRIVATE_KEY }}