It started out like most infrastructure hiccups do—innocently. A corporate security initiative introduced stricter VPN policies, requiring all sFTP traffic to tunnel through the company’s VPN. On paper, this enhanced security, centralizing access and reducing attack vectors to critical systems. But for the DevOps team, it introduced a hellish new reality: auto-deploy scripts that had meticulously worked for months suddenly started failing unexpectedly. Continuous Integration and Continuous Deployment (CI/CD) pipelines froze mid-push. Plugin updates to production environments hung for hours. What followed was a deep dive into the mechanics of deployment, networking, and security—a journey that eventually led to a clean, reliable solution involving SSH keys and a bastion host.
Contents
TL;DR
When our company enforced sFTP over a VPN, it broke our automated plugin deployments by making them VPN-dependent and flaky. Our CI/CD systems, which ran outside the VPN boundary, could no longer establish consistent connections to sFTP targets. We solved this by introducing a bastion host and refactoring deployments to use SSH key-based authentication instead of VPN-bound sFTP. The result was a far more robust, secure, and auditable deployment pipeline.
The Initial Setup: Simple, Fast—but Vulnerable
Originally, our plugin deployment architecture was simple: a Jenkins job would run a script that performed the following:
- Build the plugin artifacts.
- Connect to plugin servers via sFTP with a username and password.
- Upload the new files.
- Trigger the plugin reload via an HTTP hook.
This approach worked because the servers were inside a DMZ that allowed direct access from the Jenkins agent, and authentication was straightforward. But, as audit logs and security concerns stacked up, the IT and security departments halted that pattern. They mandated that all servers require access through a VPN—no exception. And sFTP? It had to be encrypted end-to-end through this newly implemented VPN gateway.
VPN-Only sFTP: The CI/CD Killer
On the surface, VPN-only access didn’t seem like a huge problem. We could just route Jenkins access through the VPN, right? Wrong. This opened a floodgate of complications:
- VPN credentials management: Jenkins would require persistent VPN credentials, increasing the attack surface.
- Inconsistent connectivity: VPN tunnels timed out or failed silently, especially under high load or during nightly builds.
- Debugging nightmares: Debugging failed deployments often meant checking logs across Jenkins, VPN client, and target servers. It became wildly unscalable.
Within a few days, the impact was evident. Auto-deploys became manual deploys. Engineers had to jump in during off-hours to redeploy plugins that hadn’t copied properly because of intermittent VPN issues.
Time for Modernization: Enter the SSH Key & Bastion Pattern
The writing was on the wall. We needed a solution that:
- Was secure and compliant with our new policies.
- Did not require persistent VPN access from CI/CD systems.
- Provided reliable, automatable connectivity to restricted plugin servers.
The answer came from the age-old Unix strategy of using a bastion host (aka “jump box”). A bastion is a secure, audited entry point into a private network. With carefully configured SSH keys and automated scripts, we could reset the deployment pipeline with much greater confidence.
How It Worked
The new architecture involved the following components:
- Bastion Host: A dedicated, hardened server placed at the border of the private network, acting as a gateway.
- SSH Key Pairs: Jenkins’ agent got a private SSH key; the bastion had the corresponding public key.
- ProxyCommand Directive: Using the SSH configuration file, all connections to plugin servers were routed through the bastion securely, sans VPN.
Here’s a simplified example of how the `.ssh/config` file looked:
Host plugin-server HostName internal.plugin.host User deployer ProxyJump bastion.example.com IdentityFile ~/.ssh/id_rsa
This setup allowed one command from Jenkins to silently transit through the bastion and securely connect to the plugin server—all without a flaky VPN in sight.
Benefits of the Shift
The transition wasn’t just a workaround; it was a definitive upgrade. Here’s why:
- Reliability Restored: Deployments ran successfully over 99.9% of the time post-implementation.
- Security Maintained: SSH traffic was encrypted, auditable, and had no dependency on user-based VPN tunnels.
- Simpler CI Integration: Jenkins pipelines no longer had to juggle network-level authentication. SSH keys did all the heavy lifting.
With audit logs on the bastion and fine-grained permissioning via SSH keys, the new setup also helped us pass our next internal security review with flying colors.
Downsides and Lessons Learned
Of course, there were some hurdles too:
- Initial Bastion Configuration: Setting it up with appropriate firewall rules, logging, and user permissions took time and close cooperation with IT.
- Key Rotation Management: Periodic rotation of SSH keys required scripting and policy enforcement.
- Bootstrap Challenge: We couldn’t deploy the bastion itself using the new pipeline—initial provisioning had to be manual or done through a separate secured path.
We also took this opportunity to add better logging. The bastion host was configured with session logging, rate limiting, and fail2ban policies to automatically blacklist brute-force attempts, thereby making our environment more secure than it had ever been under the VPN-based model.
Conclusion
When sFTP over VPN brought our CI/CD to a grinding halt, it was a wake-up call. CI pipelines should be agile, automated, and isolated from human friction like interactive VPN logins. By pivoting to a bastion host with SSH key-based authentication, we not only restored our auto-deploys—we reinforced their security and auditability. In true DevOps fashion, we turned a restriction into an opportunity for optimization.
For any organization wrestling with the balance between security and deployment agility, the bastion + SSH key strategy is more than just a workaround. It’s a design pattern for the modern era of automated, reliable, and secure deployments.