Cloudways is deprecating its legacy API Key authentication and moving integrations to API Access Tokens.
If you have an existing Laravel application deployed through GitHub Actions → Cloudways, this change may require updating your CI/CD workflow before the legacy API Key reaches its end of life.
Cloudways currently lists October 15, 2026 as the end-of-life date for legacy API Keys. Existing integrations can continue using API Keys during the transition, but Cloudways recommends migrating active integrations to Access Tokens before the retirement date.
This article explains how we migrated a Laravel + GitHub Actions + Cloudways deployment without changing the rest of our deployment process.
Note: This is a community-tested implementation based on Cloudways' documented Access Token API. It is not an official Cloudways guide.
Our Original Setup
Our Laravel application was deployed using this flow:
GitHub
│
│ push to develop
▼
GitHub Actions
│
│ Cloudways API Key
▼
Cloudways
│
│ Git pull
▼
Application
│
│ SSH
▼
Laravel deployment commands
The GitHub Actions workflow originally used:
- name: Cloudways Deployment
uses: roelmagdaleno/cloudways-api-git-pull-action@stable
with:
email: ${{ secrets.CW_DEV_EMAIL }}
api-key: ${{ secrets.CW_DEV_API_KEY }}
server-id: ${{ secrets.CW_DEV_SERVER_ID }}
app-id: ${{ secrets.CW_DEV_APP_ID }}
branch-name: "develop"
deploy-path: ''
The important part here is:
api-key: ${{ secrets.CW_DEV_API_KEY }}
That is the credential that needs to be migrated.
Why Do We Need to Change It?
Cloudways has introduced API Access Tokens as the replacement for the legacy API Key.
According to Cloudways, Access Tokens provide several advantages over the old API Key:
- Separate tokens can be created for different integrations.
- Tokens can have different permissions.
- Tokens can have expiration periods.
- Tokens can be revoked independently.
- Limited Access can be used to follow the principle of least privilege.
Cloudways currently recommends Limited Access when an integration only needs selected API operations.
The Problem With Our Existing GitHub Action
Our existing deployment action expects the old authentication model:
email:
api-key:
server-id:
app-id:
Cloudways' newer API authentication uses an Access Token directly as a Bearer token.
The relevant request looks like:
POST /api/v1/git/pull
Authorization: Bearer YOUR_ACCESS_TOKEN
Cloudways documents the Git deployment API using the /git/pull endpoint and Access Token authentication.
Rather than waiting for our existing third-party GitHub Action to change its authentication interface, we decided to call the Cloudways API directly from GitHub Actions using curl.
This keeps the deployment architecture almost exactly the same.
The New Deployment Flow
After the migration, our flow became:
GitHub
│
│ push to develop
▼
GitHub Actions
│
│ Cloudways Access Token
▼
Cloudways API
│
│ POST /git/pull
▼
Cloudways Application
│
│ SSH
▼
Laravel deployment commands
The important difference is simply:
OLD:
Email + API Key
↓
Cloudways API
NEW:
Access Token
↓
Cloudways API
The SSH part of the deployment remains independent.
Step 1 — Create a Cloudways Access Token
Go to your Cloudways Platform and open:
Profile → API Integration
Cloudways provides an Access Token section where you can create and manage tokens.
Create a token specifically for your GitHub Actions deployment.
For example:
Name:
Promise Assets - GitHub Actions Staging
Using a descriptive name is useful because Cloudways allows multiple Access Tokens for different integrations.
Step 2 — Choose the Appropriate Permission
For a deployment integration, you generally don't need unrestricted access to your entire Cloudways account.
Cloudways provides:
- Limited Access
- Read-Only Access
- Full Access
For an integration that performs a Git deployment, Limited Access is the appropriate approach when the required Git endpoint is available in the permission selector.
Look for the Git-related deployment operation corresponding to:
POST /git/pull
Cloudways recommends Limited Access for integrations that only require specific API operations.
Avoid giving Full Access unless your integration actually requires it.
Step 3 — Store the Token in GitHub Secrets
Do not put the Access Token directly into your YAML file.
Instead, create a GitHub repository secret:
CW_DEV_ACCESS_TOKEN
Your deployment-related secrets can then look like:
CW_DEV_ACCESS_TOKEN
CW_DEV_SERVER_ID
CW_DEV_APP_ID
CW_DEV_HOST
CW_DEV_SSH_USER
CW_DEV_SSH_PASS
Cloudways explicitly recommends treating Access Tokens as sensitive credentials and not publishing them in source code, screenshots, public repositories, or other publicly accessible locations.
Step 4 — Replace the Old Cloudways Deployment Action
Previously, we had:
- name: Cloudways Deployment
uses: roelmagdaleno/cloudways-api-git-pull-action@stable
with:
email: ${{ secrets.CW_DEV_EMAIL }}
api-key: ${{ secrets.CW_DEV_API_KEY }}
server-id: ${{ secrets.CW_DEV_SERVER_ID }}
app-id: ${{ secrets.CW_DEV_APP_ID }}
branch-name: "develop"
deploy-path: ''
We replaced that with a direct API request:
- name: Cloudways Deployment
run: |
curl --fail-with-body --request POST \
--url https://api.cloudways.com/api/v1/git/pull \
--header "Authorization: Bearer ${{ secrets.CW_DEV_ACCESS_TOKEN }}" \
--header "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "server_id=${{ secrets.CW_DEV_SERVER_ID }}" \
--data-urlencode "app_id=${{ secrets.CW_DEV_APP_ID }}" \
--data-urlencode "branch_name=develop" \
--data-urlencode "deploy_path="
This is the key change in the migration.
Why Are We Using curl?
GitHub-hosted Ubuntu runners already have curl available.
Instead of relying on a third-party GitHub Action to handle Cloudways authentication, we're making the API request ourselves.
This gives us direct control over:
Authorization
Server ID
Application ID
Branch
Deploy path
It also means that the CI/CD workflow doesn't depend on whether the third-party action has already implemented Cloudways' new Access Token authentication.
What Does Each Parameter Do?
Access Token
--header "Authorization: Bearer ${{ secrets.CW_DEV_ACCESS_TOKEN }}"
This replaces the old:
api-key: ${{ secrets.CW_DEV_API_KEY }}
Cloudways' newer authentication model uses the Access Token as a Bearer token.
Server ID
--data-urlencode "server_id=${{ secrets.CW_DEV_SERVER_ID }}"
This identifies the Cloudways server.
Application ID
--data-urlencode "app_id=${{ secrets.CW_DEV_APP_ID }}"
This identifies the application that should receive the deployment.
Branch
--data-urlencode "branch_name=develop"
Our staging environment deploys the develop branch.
Change this if your workflow uses another branch.
Deploy Path
--data-urlencode "deploy_path="
We leave the deploy path empty because the Cloudways application is already configured with its deployment directory.
Complete GitHub Actions Workflow
Here is the complete workflow used for our Laravel staging deployment:
name: Staging Deployment
on:
push:
branches: ["develop"]
pull_request:
branches: ["develop"]
types:
- closed
jobs:
deploy:
runs-on: ubuntu-latest
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Cloudways Deployment
run: |
curl --fail-with-body --request POST \
--url https://api.cloudways.com/api/v1/git/pull \
--header "Authorization: Bearer ${{ secrets.CW_DEV_ACCESS_TOKEN }}" \
--header "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "server_id=${{ secrets.CW_DEV_SERVER_ID }}" \
--data-urlencode "app_id=${{ secrets.CW_DEV_APP_ID }}" \
--data-urlencode "branch_name=develop" \
--data-urlencode "deploy_path="
- name: Executing Linux Commands
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.CW_DEV_HOST }}
username: ${{ secrets.CW_DEV_SSH_USER }}
port: 22
password: ${{ secrets.CW_DEV_SSH_PASS }}
script: |
# Navigate to Promise Assets application directory
for d in $(find $HOME /home -type d -name "public_html" 2>/dev/null); do
if [ -d "$d/Modules/CRM" ] || [ -f "$d/app/Console/Commands/SyncPermissionsCommand.php" ]; then
cd "$d"
break
fi
done
echo "Deploying in: $(pwd)"
if [ ! -f "artisan" ]; then
echo "ERROR: Could not locate promise-assets application directory!"
exit 1
fi
echo "executing composer install...."
composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
echo "clearing old cache and discovering packages...."
php ./artisan optimize:clear
php ./artisan package:discover --ansi
echo "running migration...."
php artisan migrate --force
# Sync permissions
echo "syncing permissions..."
php artisan permission:sync 2>/dev/null || true
echo "clearing cache...."
php ./artisan optimize:clear
echo "done...."
What Changed From the Old Workflow?
The rest of the workflow stays almost exactly the same.
Before
uses: roelmagdaleno/cloudways-api-git-pull-action@stable
with:
email: ${{ secrets.CW_DEV_EMAIL }}
api-key: ${{ secrets.CW_DEV_API_KEY }}
After
run: |
curl --request POST \
--url https://api.cloudways.com/api/v1/git/pull \
--header "Authorization: Bearer ${{ secrets.CW_DEV_ACCESS_TOKEN }}"
The SSH deployment remains unchanged.
This is useful because your Laravel deployment commands don't need to know anything about the Cloudways API authentication change.
Why Didn't We Add git_url?
Cloudways' current Git deployment documentation includes git_url in its API example.
However, in our existing setup the Cloudways application was already configured with its Git repository, and our previous deployment workflow successfully triggered a Git pull without explicitly providing the repository URL.
Therefore, our migration initially kept the existing application configuration and changed only the authentication mechanism.
The practical lesson is:
Don't unnecessarily change a working deployment configuration while migrating authentication.
If your Cloudways API response indicates that git_url is required for your particular application/API configuration, add it to the request.
For example:
--data-urlencode "git_url=${{ secrets.CW_DEV_GIT_URL }}"
The repository URL should then be stored as a GitHub Secret rather than hard-coded if you prefer to keep your deployment configuration centralized.
Testing the Migration
Before deleting the old API Key, test the new workflow.
A safe migration sequence is:
1. Create Access Token
↓
2. Configure GitHub Secret
↓
3. Update GitHub Actions
↓
4. Push to develop
↓
5. Verify Cloudways Git deployment
↓
6. Verify SSH deployment
↓
7. Test another deployment
↓
8. Remove old API Key
Don't immediately delete the old credential.
Keeping it temporarily gives you a fallback while validating the new authentication.
Useful Error Checks
401 Unauthorized
If you receive something like:
401 Unauthorized
check:
- Access Token value
- GitHub Secret name
- Token expiration
-
Authorization: Bearersyntax
Make sure the secret is referenced exactly:
${{ secrets.CW_DEV_ACCESS_TOKEN }}
403 Forbidden
A 403 generally indicates an authorization/permission problem.
Check the Access Token's Limited Access configuration and make sure it has permission for the Git deployment operation required by your workflow.
Cloudways Access Tokens can be configured with specific API endpoint permissions.
400 Bad Request
A 400 usually means that one or more parameters aren't acceptable.
Check:
server_id
app_id
branch_name
deploy_path
If your Cloudways configuration/API response requires the repository URL, also provide:
git_url
Cloudways provides an API Playground that can be useful for testing API requests independently from GitHub Actions.
Testing With the Cloudways API Playground
Cloudways provides an API Playground where you can authorize with an Access Token and test API endpoints.
This can be useful before troubleshooting GitHub Actions.
The basic process is:
- Open the Cloudways API Playground.
- Click Authorize.
- Select the Access Token authentication option.
- Enter your Access Token.
- Select the relevant Git endpoint.
- Provide the required parameters.
- Execute the request.
Cloudways notes that actions performed through the API Playground operate against the authenticated account, so use care when testing endpoints that modify infrastructure or applications.
Don't Confuse API Authentication With SSH Authentication
One thing that initially made this migration confusing for us was that the deployment has two separate authentication mechanisms.
Cloudways API
GitHub Actions
│
│ Access Token
▼
Cloudways API
│
▼
Git Pull
SSH
GitHub Actions
│
│ SSH credentials
▼
Cloudways Server
│
▼
Laravel commands
The Cloudways Access Token replaces the API Key.
It does not replace your SSH credentials.
Therefore, this can remain:
uses: appleboy/ssh-action@v1.0.3
with your existing SSH configuration.
Our Laravel Deployment Commands
Once Cloudways has pulled the latest code, our workflow connects over SSH and performs the Laravel deployment tasks.
The commands include:
composer install
followed by:
php artisan optimize:clear
php artisan package:discover --ansi
Then migrations:
php artisan migrate --force
Then our application-specific permission synchronization:
php artisan permission:sync
And finally:
php artisan optimize:clear
This part of the deployment does not depend on the Cloudways API Key.
GitHub Secrets After Migration
Our new secrets look like:
CW_DEV_ACCESS_TOKEN
CW_DEV_SERVER_ID
CW_DEV_APP_ID
CW_DEV_HOST
CW_DEV_SSH_USER
CW_DEV_SSH_PASS
The old credentials:
CW_DEV_EMAIL
CW_DEV_API_KEY
are no longer required by the new Cloudways API call.
We recommend keeping the old API Key temporarily during migration and removing it only after the new deployment has been successfully tested.
Security Considerations
An Access Token is effectively a credential for your Cloudways account/API.
Never commit this:
CW_DEV_ACCESS_TOKEN: "actual-token-here"
to your repository.
Instead use:
${{ secrets.CW_DEV_ACCESS_TOKEN }}
Cloudways explicitly advises users not to expose Access Tokens in public repositories, screenshots, support tickets, chats, or other publicly accessible locations.
Also consider creating a dedicated token for each deployment environment.
For example:
Production:
Cloudways - GitHub Actions - Production
Staging:
Cloudways - GitHub Actions - Staging
This makes revocation and troubleshooting much easier.
Cloudways supports multiple Access Tokens, with separate permissions and expiration settings.
One More Improvement: Don't Use Full Access Unless You Need It
When creating the token, you may see options such as:
Limited Access
Read-Only Access
Full Access
For a deployment integration, don't automatically choose Full Access.
If the Git deployment endpoint is available under Limited Access, select only the required Git operation.
This follows the principle of least privilege and limits what a compromised CI/CD credential can do. Cloudways itself recommends Limited Access for integrations that only require selected API operations.
What About the Old API Key?
Cloudways currently states that the legacy API Key is scheduled for end of life on:
October 15, 2026
Existing users can continue using the old API Key during the transition period, but Cloudways recommends migrating active integrations before the retirement date. After the deprecation period, legacy API Keys will be revoked and integrations that still depend on them may stop working.
So this isn't just a cosmetic change.
If your CI/CD pipeline still contains:
api-key: ${{ secrets.CW_DEV_API_KEY }}
it's worth identifying and migrating it before the deadline.
Final Architecture
After the migration, our deployment architecture looks like this:
GitHub
│
│ push
▼
GitHub Actions
│
│ Access Token
▼
Cloudways API
│
│ POST /git/pull
▼
Cloudways Application
│
│ latest code
▼
SSH Connection
│
┌─────────────┼──────────────┐
│ │ │
▼ ▼ ▼
Composer Migrations Permissions
│ │ │
└─────────────┼──────────────┘
│
▼
Cache Clear
│
▼
Deployed
The main takeaway is that you don't necessarily need to redesign your entire CI/CD pipeline just because Cloudways is retiring the legacy API Key.
If your existing Cloudways application is already configured for Git deployment, you can keep the surrounding deployment process and replace the legacy API authentication with an Access Token-based /git/pull request.
Conclusion
Cloudways' move from API Keys to Access Tokens requires existing CI/CD integrations to be reviewed before October 15, 2026.
For a Laravel application using GitHub Actions, one practical migration path is:
Legacy API Key
↓
Cloudways /git/pull
↓
Replace with
↓
Access Token + Bearer authentication
↓
Cloudways /git/pull
↓
Continue using SSH for Laravel deployment
In our case, this allowed us to migrate the Cloudways authentication layer without changing the Composer, migration, permission-sync, or cache-clearing stages of the existing Laravel deployment.
If you're currently using a third-party GitHub Action that only supports Cloudways' legacy API Key, a direct API call from GitHub Actions is one option worth considering while migrating to the new authentication system.
Official Cloudways documentation:
Top comments (0)