Introduction
Note
Scope: this guide targets classic ASP.NET Framework projects built with Visual Studio and MSBuild. Modern SDK-style .NET applications normally use the dotnet CLI for restore, build, test and publish.
When this deployment approach makes sense
FTP is not the first deployment mechanism I would choose for a new cloud-native application. It is still practical to maintain a classic ASP.NET Framework website on traditional IIS hosting when the provider exposes FTP or FTPS as the available deployment interface.
In that situation, Azure Pipelines can still provide a useful CI/CD workflow: restore packages, build the application, run tests, publish to a staging directory, and transfer the resulting files to the server.
CI/CD pipeline flow
The pipeline performs five straightforward steps:
Runs after a commit to the
masterbranch.Uses a Microsoft-hosted Windows agent.
Restores NuGet packages and builds the solution in
Release.Runs tests with
VSTest@2.Uploads the published website with
FtpUpload@2.
If your repository uses main or another deployment branch, change the trigger accordingly.
Complete Azure Pipelines YAML
This is a complete pipeline that uses direct FTP credentials, supported by FtpUpload@2. Replace the placeholder server address, username, and password before running it.
# ASP.NET
# Build and test ASP.NET projects.
# Add steps that publish symbols, save build artifacts, deploy, and more:
# https://docs.microsoft.com/azure/devops/pipelines/apps/aspnet/build-aspnet-4
trigger:
- master
pool:
vmImage: 'windows-latest'
variables:
solution: '**/*.sln'
buildPlatform: 'Any CPU'
buildConfiguration: 'Release'
steps:
- task: NuGetToolInstaller@1
- task: NuGetCommand@2
inputs:
restoreSolution: '$(solution)'
- task: VSBuild@1
inputs:
solution: '$(solution)'
msbuildArgs: '/p:DeployOnBuild=true /p:DeployDefaultTarget=WebPublish /p:WebPublishMethod=FileSystem /p:SkipInvalidConfigurations=true /p:publishUrl=$(build.artifactstagingdirectory)\website'
platform: '$(buildPlatform)'
configuration: '$(buildConfiguration)'
- task: VSTest@2
inputs:
platform: '$(buildPlatform)'
configuration: '$(buildConfiguration)'
- task: FtpUpload@2
inputs:
credentialsOption: 'inputs'
serverUrl: 'ftp://127.0.0.1'
username: 'ftp-user-name'
password: 'P@ssword'
rootDirectory: '$(build.artifactStagingDirectory)/website'
filePatterns: '**'
remoteDirectory: '/wwwroot' # '/wwwroot/$(Build.BuildId)/'
clean: false
cleanContents: false
preservePaths: true
trustSSL: false
Warning
Security warning: the YAML above intentionally shows all inputs together so the complete task is easy to understand and copy. Do not commit a real FTP password to source control. The safer configuration is shown below.
How the pipeline works
1. Trigger and build agent
The pipeline runs for commits to master and uses windows-latest. A Windows agent is appropriate here because the build relies on Visual Studio/MSBuild tasks for a classic ASP.NET Framework solution.
2. NuGet restore
NuGetToolInstaller@1 makes the required NuGet tooling available. NuGetCommand@2 then restores packages for the solution selected by **/*.sln.
3. Build and file-system publish
VSBuild@1 builds the solution in Release. The MSBuild arguments enable web publishing and write the deployable website to $(build.artifactstagingdirectory)\website.
The FTP task later uses that same directory as its upload source. This relationship is important: if the publish path changes, update rootDirectory as well.
4. Tests before deployment
VSTest@2 executes before the FTP task. Under the normal Azure Pipelines success condition, a failed test prevents the later deployment step from running.
5. FTP upload
FtpUpload@2 uses credentialsOption: 'inputs', so serverUrl, username and password are supplied directly to the task. Microsoft still documents this authentication mode alongside the service-endpoint option.
The task uploads everything matched by ** from the published website directory to /wwwroot, preserving the relative directory structure.
The main security problem: credentials in YAML
The pipeline is easy to understand when all values are visible together, but a real FTP password must not be committed to azure-pipelines.yml. Microsoft recommends never storing sensitive values as plain text in pipeline YAML.
A minimal improvement is to keep the deployment structure unchanged and replace the sensitive values with Azure DevOps variables:
- task: FtpUpload@2
inputs:
credentialsOption: 'inputs'
serverUrl: '$(ftpServerUrl)'
username: '$(ftpUsername)'
password: '$(ftpPassword)'
rootDirectory: '$(build.artifactStagingDirectory)/website'
filePatterns: '**'
remoteDirectory: '/wwwroot'
clean: false
cleanContents: false
preservePaths: true
trustSSL: false
Create ftpPassword as a secret variable in the Azure DevOps pipeline settings or a protected variable group. You can also move the username and server URL to variables to separate environment configuration from the pipeline definition.
Note
Important: secret masking reduces accidental disclosure in logs, but it is not a reason to print or echo secrets. Treat pipeline logs and task inputs carefully.
What about an FTP service connection?
FtpUpload@2 also supports credentialsOption: 'serviceEndpoint'. Microsoft documents this using an FTP service connection backed by a generic service connection. That can be a good alternative when the required service-connection type is available in your Azure DevOps organization.
It is not required for this pipeline. The direct inputs mode shown above is a documented FtpUpload@2 configuration and avoids making the tutorial depend on a service-connection option that may not be exposed in every organization or current UI configuration.
FTP and FTPS behavior in FtpUpload@2
The task supports FTP and secure FTP over TLS. Microsoft documents an important detail: even when the URL starts with ftp://, the task will make a secure connection when the target server supports FTPS.
If you specify ftps://, secure transport becomes mandatory. The connection fails when the target server does not support FTPS.
Server URL | Behavior | Use when |
|---|---|---|
ftp:// | Can use a secure connection when the server supports FTPS, but does not require FTPS. | You need compatibility with a server whose capabilities may vary. |
ftps:// | Requires a secure FTPS connection. | You know the server supports FTPS and want secure transport to be mandatory. |
FTPS and SFTP are not the same protocol. SFTP runs over SSH. Do not assume that an SFTP-only server can be used with FtpUpload@2.
Understand the FtpUpload@2 options before changing them
clean
With clean: true, the task deletes the remote directory before uploading. This is potentially destructive and should only be enabled when the remote directory is entirely owned by this deployment.
cleanContents
When clean is false, cleanContents: true clears the contents of the remote directory. This can solve problems with obsolete deployment files, but it can also remove server-side files that are intentionally retained.
preservePaths
preservePaths: true preserves relative paths from the local source. For a web application this is normally important because assemblies, views, scripts, styles and other assets must retain their directory structure.
trustSSL
trustSSL: false is the safer default. Enabling certificate trust is intended for scenarios such as self-signed certificates. Do not use it simply to hide an FTPS certificate validation problem.
Note
Why both cleanup options are false in the example: the deployment does not deliberately erase the remote application before uploading. That reduces destructive behavior, but it also means obsolete files from an earlier release can remain on the server. Choose the cleanup strategy based on the application and hosting environment.
What happens to the live application during FTP deployment?
This is the most important operational limitation of the approach. FTP deployment copies files to the destination rather than providing an atomic application switch. If you upload directly to the directory used by the running website, files are replaced over time.
For an ASP.NET Framework application on IIS, changing files such as assemblies or configuration can also cause the application to restart or recycle. During a sufficiently large deployment, the server can temporarily contain a mixture of files from two releases.
The practical consequences depend on the application, hosting configuration, and files being changed, but possible effects include temporary errors, application restarts, and inconsistent static assets while the upload is in progress.
Warning
Plan deployments accordingly: for a small, low-traffic site, this trade-off may be acceptable. For an application that requires zero-downtime or strongly consistent releases, direct FTP upload to the live directory is a poor deployment boundary.
When to choose another deployment method
Use this approach when FTP or FTPS is the interface the hosting environment actually gives you. If you control the infrastructure or the platform provides a stronger deployment mechanism, prefer that mechanism.
For traditional IIS environments, Web Deploy can provide more deployment-specific capabilities. Cloud platforms can offer staging environments, deployment slots, or other release mechanisms that make it possible to prepare a release before switching traffic to it.
The goal is not to avoid FTP at all costs. It is to understand the trade-off: FtpUpload@2 is simple and useful for constrained hosting, but simplicity comes with weaker release semantics than a deployment system designed around application versions and atomic switching.
Frequently Asked Questions
Is FtpUpload@2 still available in Azure DevOps?
Yes. Microsoft continues to document FtpUpload@2 for uploading files through FTP or FTPS.
Is credentialsOption: inputs supported?
Yes. FtpUpload@2 supports both serviceEndpoint and inputs. With the latter, the task accepts serverUrl, username, and password.
Should I put the FTP password in YAML?
No. Keep the complete example as a reference for the task structure, but store a real password as an Azure DevOps secret variable, protected variable-group value or another appropriate secret store.
Should I change ftp:// to ftps://?
Use ftps:// when the target supports FTPS and you want to require a secure connection. Microsoft documents that specifying FTPS causes the task to fail if the server cannot establish the secure connection.
Does FtpUpload@2 support SFTP?
Do not treat SFTP as equivalent to FTP or FTPS. SFTP is an SSH-based protocol and requires an appropriate SFTP-capable deployment mechanism.
Why are clean and cleanContents false?
It deliberately avoids deleting the destination before upload. The trade-off is that files no longer present in the new release can remain remotely. Review this behavior for your application instead of changing either option blindly.
Can users be affected while the deployment runs?
Yes. Direct FTP deployment is not atomic. Files are replaced while the website can still be serving requests, and changes to an ASP.NET Framework application can trigger application restarts.
References and source code
Conclusion
Azure DevOps and FtpUpload@2 can provide a compact CI/CD pipeline for an ASP.NET Framework website when FTP-compatible hosting is a real constraint. The pipeline restores packages, builds the application, runs tests, and uploads the published output with a small amount of YAML.
The important part is knowing where the simple solution has sharp edges. Never commit real credentials, prefer secure transport when the server supports it, understand the cleanup flags, and remember that an FTP upload directly into a live website is not an atomic deployment.
When those trade-offs are acceptable, this approach remains practical. When they are not, move the release boundary to a deployment mechanism designed for controlled application updates.