Web hosting · Practical guide

Deploy website files with SSH keys, rsync previews and limited access

Separate deployment access from account administration, preview rsync changes and verify a release without exposing credentials or deleting target data blindly.

Archive placement: July 2023

Programming code on a computer screen

A designer or deployment process often needs to change one website, not administer your billing account, mailboxes and every other service. Separate identities and a deliberate file-transfer workflow reduce accidental changes and make access easier to remove. The important controls are the scope of the account, the destination path and the verification after transfer.

Create an identity for the work being performed

Use an individual or purpose-specific deployment account instead of sharing the primary Control Panel login. On a VPS, give that account ownership or group access to the intended release location without granting unrestricted sudo merely to upload files. On shared hosting, use the provider's supported account-scoping tools.

Define the exact site, access window and person responsible for removing the credential. If automated deployment is required, keep its key separate from an administrator's personal key. Consider what the account can read as well as what it can write: private configuration and unrelated sites may live beside the deployment directory.

Use an encrypted transport and verify host identity

Create a dedicated key on the deployment workstation
umask 077
ssh-keygen -t ed25519 -a 64 \
  -f ~/.ssh/site_deploy_ed25519 -C 'website-deployment'

Protect the private key and install only its public key for the intended server account through an authorized setup process. Verify the server host-key fingerprint independently before the first transfer. SFTP runs over SSH; FTP with TLS is a separate protocol and must be explicitly supported and configured.

Open a server session using the intended key
ssh -o IdentitiesOnly=yes -i ~/.ssh/site_deploy_ed25519 \
  deploy@server.example.com

Confirm the account identity and target directory in this session. A successful login does not prove that the account has only the intended privileges, so review the permissions and any authorized_keys restrictions as part of account setup.

Preview the transfer before writing the release

Preview changes to a prepared release directory
rsync -av --dry-run --itemize-changes \
  --exclude='.git/' --exclude='.env' \
  -e 'ssh -o IdentitiesOnly=yes -i ~/.ssh/site_deploy_ed25519' \
  ./site/ deploy@server.example.com:/srv/www/example-app/releases/test/

The trailing slash on ./site/ means transfer the directory's contents. Verify that both source and destination refer to the intended site. Review the itemized output for unexpected paths and file counts. The example excludes repository metadata and the environment file; prepare production configuration separately using a controlled process.

This example deliberately omits --delete. That option removes target entries absent from the source and can erase uploads or unrelated data if the path or application layout is wrong. If your deployment design needs deletion, establish exclusions, a tested backup and a reviewed dry run before adding it.

Transfer the reviewed release and verify its contents

Perform the same transfer after reviewing the preview
rsync -av --itemize-changes \
  --exclude='.git/' --exclude='.env' \
  -e 'ssh -o IdentitiesOnly=yes -i ~/.ssh/site_deploy_ed25519' \
  ./site/ deploy@server.example.com:/srv/www/example-app/releases/test/

Check the transfer exit status and any permission errors. Avoid uploading directly over a live application if that can expose a half-updated codebase. A release-directory and cutover process, or an application-supported maintenance window, can separate file transfer from activation.

Do not transfer a private SSH key into the website tree. Keep configuration secrets, SQL exports and deployment archives outside publicly served paths. An archive containing the whole account can carry far more access than the recipient needs for a website change.

Validate the release as a running application

Check the runtime requirements, file ownership and writable directories before switching traffic. Verify a representative URL and application workflow, then inspect logs. A byte-for-byte file transfer does not update a database schema, install dependencies or confirm that an application can connect to its services.

Example public response check after activation
curl --fail --show-error --silent \
  --connect-timeout 5 --max-time 15 \
  -o /dev/null -w 'status=%{http_code} total=%{time_total}s
' https://example.com/

If the release fails, return to the previous known-good release or follow the application's rollback procedure. Coordinate database rollback separately; replacing old code can be unsafe after an incompatible schema change. Keep a written record of the deployed release and the tests that passed.

Close the access lifecycle

  • Remove temporary public keys and unused transfer accounts when the work ends.
  • Rotate exposed credentials instead of just deleting a message that contained them.
  • Keep each deployment identity attributable to a person or process.
  • Review remaining files and archives for public exposure after the release.

Limited access is most useful when it remains limited over time. A temporary upload account left active for years becomes part of the attack surface even if the original project is finished.

Primary documentation

← Back to all guidesDiscuss your hosting setup ↗