Jake McMurchie saxophonist and WordPress developer

Jake McMurchie

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…

Hosting settings…

Bitbucket settings…

In cPanel…

Back to Bitbucket again…

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&