GitHub workflow action specifically for WordPress deployments on Cloudways managed servers. Originally forked from official Cloudways action that has not been updated in a long time.
This should match the Public IP field under Master Credentials in the dashboard for the server where your application is deployed.
This should match the Username field under Master Credentials in the dashboard for the server where your application is deployed.
This should match your private key that matches with the key loaded into SSH Public Keys in the dashboard for the server where your application is deployed.
One of these combinations must be specified.
TARGET- provide the entire remote path.TARGET_BASE- provide the base path to come before the folder name.FOLDER_NAME- provide the value fromFolderfor your application inApplication Settingsin Cloudways.
KNOWN_HOSTS- Known hosts entry for the server. Strongly recommended — omitting disables host key verification. See KNOWN_HOSTS below.SSH_PASSPHRASE- Passphrase for the SSH private key (required by some managed hosts)REMOTE_PORT(defaults to 22)SOURCE(defaults topublic/)ARGSorRSYNC_ARGS(default-azvr --inplace --exclude='.*' --no-perms --no-times)EXCLUDE_FILE(see below on handling excludes)EXTRA_EXCLUDE(see below on handling excludes)CLOUDWAYS_API_TOKEN(see below on resetting permissions)CLOUDWAYS_SERVER_ID(required whenCLOUDWAYS_API_TOKENis set)CLOUDWAYS_APP_ID(required whenCLOUDWAYS_API_TOKENis set)RESET_PERMISSIONS_OWNERSHIP(master_userorsys_user; omit to use the Cloudways default)CLOUDWAYS_API_URL(defaults tohttps://api.cloudways.com/api/v1)
Without a KNOWN_HOSTS value the action connects with StrictHostKeyChecking=no, which disables host key verification and leaves the deploy vulnerable to MITM attacks. Setting this input enables StrictHostKeyChecking=yes.
Get the value from your server (run this once locally):
ssh-keyscan -H your-server-public-ipCopy the output and store it as a GitHub secret, then pass it to the action:
- name: Deploy to Cloudways
uses: fkwdigital/github-workflow-action-cloudways-application-wordpress@v1
with:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
KNOWN_HOSTS: ${{ secrets.KNOWN_HOSTS }}
REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
REMOTE_USER: ${{ secrets.REMOTE_USER }}
FOLDER_NAME: ${{ secrets.FOLDER_NAME }}The private key file is deleted from the runner when the action exits.
The deploy itself uses only SSH and rsync. It does not need the Cloudways API, so the retirement of the legacy Cloudways API key (October 15, 2026) does not affect it.
If you also set CLOUDWAYS_API_TOKEN, the action resets the application's file and folder permissions after a
successful rsync, then waits for Cloudways to finish. Skip CLOUDWAYS_API_TOKEN to keep the SSH-only behavior.
The action uses a Cloudways API access token, not the legacy email + API key. The token goes in the
Authorization: Bearer header.
Only the primary Cloudways account owner can create access tokens. Team member accounts do not see the API Integration menu.
- Log in to the Cloudways Platform. From the profile dropdown (top right), click API Integration.
- In the Access Token Details section, click Create Access Token.
- Access Token Name: use a name that identifies the repository, e.g.
GitHub Deploy - mysite. The name does not affect permissions. - Expiration: choose
1 day,1 month,3 months,6 months,1 yearorNever. Pick the shortest period you are willing to rotate on. When the token expires, the API step fails with HTTP 401 until you update the secret. - Scope: choose Limited Access, then expand each category and select only these two endpoints rather than ticking a whole category:
| Endpoint | Category | Purpose |
|---|---|---|
POST /app/manage/reset_permissions |
App Management | Reset file and folder permissions for the application |
GET /operation/{id} |
Operation | Check the status of the queued reset until it completes |
Do not choose Read-Only Access, because it only covers GET requests and cannot run the reset. Do not choose
Full Access, because it grants far more than this action needs. Limited Access is marked Beta by Cloudways,
so the category names on screen may change.
6. Click Create Access Token and copy the token right away. Cloudways shows it only once. It cannot be viewed
or regenerated later; if you lose it, revoke it and create a new one.
7. Save the token as a repository secret, e.g. CLOUDWAYS_API_TOKEN. Never commit it or paste it into logs,
issues or screenshots.
To check the permissions later, open the token's three-dot menu in Access Token Details and choose View Scopes. To stop the token working immediately, choose Revoke.
You can test the token before using it in a workflow. In the Cloudways API Playground, click Authorize, open the
Access Token tab, paste the token, and try POST /app/manage/reset_permissions.
Open the application in the Cloudways Platform. The URL looks like
https://platform.cloudways.com/server/<SERVER_ID>/application/<APP_ID>/.... Store both values as secrets or
variables.
- HTTP 401: token is wrong, expired or revoked, or the secret name is misspelled.
- HTTP 403: token is missing one of the permissions listed above. Check them with View Scopes.
- HTTP 400/422:
CLOUDWAYS_SERVER_IDorCLOUDWAYS_APP_IDis wrong.
If the API step fails, the files are already deployed. Only the permission reset failed, and the job fails so you notice.
See example in examples/deploy.yml and in this doc. Copy it into .github/workflows/ of the repository you
deploy from, not this action's repository.
These files are excluded by default from deployment to protect your live WordPress installation. You can override this list with EXCLUDE_FILE, or append to this list with EXTRA_EXCLUDE
const ALWAYS_EXCLUDE = [
'*~',
'.git',
'.github',
'.gitignore',
'.DS_Store',
'.svn',
'.cvs',
'*.bak',
'*.swp',
'Thumbs.db',
'*.log',
'.env',
'.smushit-status',
'.gitattributes',
'/db-config.php',
'/index.php',
'/wp-activate.php',
'/wp-admin/',
'/wp-app.php',
'/wp-atom.php',
'/wp-blog-header.php',
'/wp-comments-post.php',
'/wp-commentsrss2.php',
'/wp-config.php',
'/wp-content/advanced-cache.php',
'/wp-content/backup-db/',
'/wp-content/blogs.dir/',
'/wp-content/breeze-config/',
'/wp-content/cache/',
'/wp-content/drop-ins/',
'/wp-content/index.php',
'/wp-content/mu-plugins/',
'/wp-content/mysql.sql',
'/wp-content/object-cache.php',
'/wp-content/plugins/',
'/wp-content/themes/index.php',
'/wp-content/themes/twenty*',
'/wp-content/themes/variations/',
'/wp-content/upgrade*',
'/wp-content/uploads/',
'/wp-content/webp-express',
'/wp-content/wp-cache-config.php',
'/wp-cron.php',
'/wp-feed.php',
'/wp-includes/',
'/wp-links-opml.php',
'/wp-load.php',
'/wp-login.php',
'/wp-mail.php',
'/wp-pass.php',
'/wp-rdf.php',
'/wp-register.php',
'/wp-rss.php',
'/wp-rss2.php',
'/wp-salt.php',
'/wp-settings.php',
'/wp-signup.php',
'/wp-trackback.php',
'/xmlrpc.php'
];Keep all default excludes and add more:
EXTRA_EXCLUDE: 'custom-config.php,/wp-content/custom-cache/'Provide your own exclude file (replaces ALL defaults):
EXCLUDE_FILE: './deploy/excludes.txt'Create excludes.txt with one pattern per line:
*.log
.env
/wp-config.php
node_modules/
When specifying excludes, be sure that the path matches exactly to the file you are looking to exclude. Vague lines will create unintended consequences! For example, adding index.php will also exclude the index.php file in your theme folders. Use /index.php to specify the root index file only.
name: Deploy to Ubuntu Server
on:
push:
branches: [prd]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Deploy to Server
uses: fkwdigital/github-workflow-action-cloudways-application-wordpress@v1
with:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
REMOTE_USER: ${{ secrets.REMOTE_USER }}
SOURCE: "./public/"
FOLDER_NAME: ${{ env.CLOUDWAYS_REMOTE_FOLDER }}- name: Deploy with Additional Excludes
uses: fkwdigital/github-workflow-action-cloudways-application-wordpress@v1
with:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
REMOTE_USER: ${{ secrets.REMOTE_USER }}
SOURCE: "./public/"
FOLDER_NAME: ${{ env.CLOUDWAYS_REMOTE_FOLDER }}
EXTRA_EXCLUDE: '/wp-content/custom-cache/,debug.log,*.tmp'- name: Deploy with Delete After
uses: fkwdigital/github-workflow-action-cloudways-application-wordpress@v1
with:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
REMOTE_USER: ${{ secrets.REMOTE_USER }}
SOURCE: "./public/"
FOLDER_NAME: ${{ env.CLOUDWAYS_REMOTE_FOLDER }}
ARGS: "-azvr --inplace --exclude='.*' --no-perms --no-times"- name: Deploy to Specific Folder
uses: fkwdigital/github-workflow-action-cloudways-application-wordpress@v1
with:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
REMOTE_USER: ${{ secrets.REMOTE_USER }}
TARGET_BASE: '/var/www'
FOLDER_NAME: 'mysite'
SOURCE: './public/'- name: Deploy to User Directory
uses: fkwdigital/github-workflow-action-cloudways-application-wordpress@v1
with:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
REMOTE_USER: ${{ secrets.REMOTE_USER }}
TARGET: '/home/username/public_html'
SOURCE: './public/'- name: Deploy with Custom Exclude File
uses: fkwdigital/github-workflow-action-cloudways-application-wordpress@v1
with:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
REMOTE_USER: ${{ secrets.REMOTE_USER }}
SOURCE: "./public/"
FOLDER_NAME: ${{ env.CLOUDWAYS_REMOTE_FOLDER }}
EXCLUDE_FILE: './deploy/excludes.txt'- name: Deploy with Custom SSH Port
uses: fkwdigital/github-workflow-action-cloudways-application-wordpress@v1
with:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
REMOTE_USER: ${{ secrets.REMOTE_USER }}
REMOTE_PORT: 2222
SOURCE: "./public/"
FOLDER_NAME: ${{ env.CLOUDWAYS_REMOTE_FOLDER }}- name: Deploy with SSH Passphrase
uses: fkwdigital/github-workflow-action-cloudways-application-wordpress@v1
with:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
SSH_PASSPHRASE: ${{ secrets.SSH_PASSPHRASE }}
REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
REMOTE_USER: ${{ secrets.REMOTE_USER }}
SOURCE: './public/'
FOLDER_NAME: ${{ env.CLOUDWAYS_REMOTE_FOLDER }}- name: Deploy and Reset Permissions
uses: fkwdigital/github-workflow-action-cloudways-application-wordpress@v1
with:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
REMOTE_USER: ${{ secrets.REMOTE_USER }}
SOURCE: './public/'
FOLDER_NAME: ${{ env.CLOUDWAYS_REMOTE_FOLDER }}
CLOUDWAYS_API_TOKEN: ${{ secrets.CLOUDWAYS_API_TOKEN }}
CLOUDWAYS_SERVER_ID: ${{ secrets.CLOUDWAYS_SERVER_ID }}
CLOUDWAYS_APP_ID: ${{ secrets.CLOUDWAYS_APP_ID }}This is expected with --no-times flag. Files are deployed successfully.
Check available disk space on remote server. If you added your own rsync flags, consider using --delete-after flag to remove old files.
Verify your TARGET, TARGET_BASE, and/or FOLDER_NAME is correct. FOLDER_NAME needs to match the Folder field provided in the Cloudways Application Settings exactly.
GPL-3.0-or-later. See LICENSE.
- Action Issues: Open an issue on this repository
- Cloudways API: Cloudways Support
- Documentation: Cloudways API Docs