A basic CI/CD deployment from Bitbucket to cPanel
15/04/2026
Following on from my previous post on how to deploy direct from Bitbucket to Cloudways, I’ve found myself having to reply to a more “conventional” web server (in this case the excellent Krystal) which uses cPanel. So:
Initial context…
- My code is held in a git repository on Bitbucket
- The files are hosted by a web server running cPanel
Hosting settings…
- Make sure SSH is enabled
Bitbucket settings…
- Go to Repository settings of the repo
- Under Pipelines click Settings
- Enable pipelines
- Also under Pipelines click Repository variables
- In the Name field type “SFTP_USERNAME”, and in the Value field type (or copy/paste) the username of your cPanel account. Leave secured ticked and click Add
- Again, in the Name field type “SFTP_LOCATION”, and in the Value field type (or copy/paste) the IP address of your Cloudways application’s Public IP (under Access details > Application Credentials). Leave secured ticked and click Add
- Again under Pipelines click SSH keys
- Click Generate keys and the Copy public key
In cPanel…
- Go to Security then SSH Access then click Manage SSH keys
- Click Import key
- Type a name for the key (e.g., “bitbucket_deployment”), paste the public key copied from Bitbucket into the public key field, and click Import
- Back on the SSH Access page, check the “authorisation status” of the key and authorise it if required.
- Back on the cPanel homepage copy the Public IP
Back to Bitbucket again…
- Still on the SSH keys screen under Known hosts, paste the IP address into the Host address field and click Fetch
- When the key is returned, click Add host
Note: Krystal uses non-standard ports for SSH! So make you append “:[port_number]” to the end of the IP address.
Back in my code…
Create a new file called “bitbucket-pipelines.yml” and add this into the file:
pipelines:
branches:
main:
- step:
name: "Deploy to production"
script:
- pipe: atlassian/sftp-deploy:0.10.0
variables:
USER: $SFTP_USERNAME
SERVER: $SFTP_LOCATION
REMOTE_PATH: "public_html/"
LOCAL_PATH: "./"
Then, in the terminal:>git add --all
>git commit -m "Added Bitbucket pipelines config"
>git push
To check your deployment, in Bitbucket go back to the Repository main page and click Pipelines.
This should work. However, I found that, although the files were successfully SCP’d to the web server, the permissions for the files meant the web server served a 403 error. Closer inspection and this was because the files were given 666 permissions and folders 777 and Krystal’s web servers deem this too permissive, with 644 and 755 recommended.
According to this post on Bitbucket’s support website this is because “Bitbucket Pipelines sets the umask to 0022, causing cloned files to have 666 permissions”. So with thanks to Curtis on the Bitbucket community forum, I suggest using the following:
pipelines:
branches:
"*":
- step:
name: "Build and Set Permissions"
script:
- find . -type d -exec chmod 755 {} \;
- find . -type f -exec chmod 644 {} \;
artifacts:
- files/**
main:
- step:
name: "Deploy to production"
clone:
enabled: false
script:
- pipe: atlassian/sftp-deploy:0.10.0
variables:
USER: $SFTP_USERNAME
SERVER: $SFTP_LOCATION
REMOTE_PATH: "/public_html/"
LOCAL_PATH: "./"
This adds a step in the pipeline script that sets the correct permissions and stores the files, then, in the deployment step, prevents the step from cloning the files so it uses the previously created (with corrected permissions) files and folders.
Note: to deal with a non-standard SSH port (such as that favoured by Krystal), make sure your bitbucket-pipelines.yml file looks like this:
pipelines:
branches:
main:
- step:
name: "Deploy to production"
script:
- pipe: atlassian/sftp-deploy:0.10.0
variables:
USER: $SFTP_USERNAME
SERVER: $SFTP_LOCATION
REMOTE_PATH: "public_html/"
LOCAL_PATH: "./"
EXTRA_ARGS: "-P [port number]"
Problems I encountered..
Getting the indenting / formatting of the .yml file was problematic. This was helpful:
https://bitbucket.org/product/pipelines/validator
I’ve also found I have to be careful about setting the path correctly.
More info…
https://bitbucket.org/product/features/pipelines/integrations?category=deployment&