Secure HTTPS File Transfer with curl and Apache on RHEL

Deepak Prasad
Tested on File server: RHEL 10.2 at 192.168.56.116 (vm1.lab.example)
Client: RHEL 10.2 at 192.168.56.220 (vm2.lab.example)
Package httpd 2.4.63
mod_ssl 2.4.63
httpd-tools 2.4.63 (htpasswd)
curl 8.12.1
openssl 3.5.5
Applies to RHEL, Rocky Linux, AlmaLinux, and Fedora (httpd, firewalld, SELinux). Client curl commands work on any Linux host with curl installed.
Privilege root or sudo on the RHEL-family HTTPS server; on the client, any user with network access once the hostname resolves (copy the PEM to your home directory; adding a hosts entry or system trust store requires sudo)
Scope RHEL-family server setup: Apache with TLS and WebDAV outside DocumentRoot, HTTP basic auth before starting httpd, verified HTTPS with curl --cacert, upload with curl -T, download with curl -o. Does not cover Debian or Ubuntu apache2 setup, mutual TLS client certificates, or object storage APIs.
Related guides Copy files between Linux servers
curl command cheat sheet
wget command
systemctl command
Open firewall port Linux

You need two hosts: an RHEL-family Apache file server on vm1.lab.example and a client on vm2.lab.example. Server-side steps use dnf, httpd, firewalld, and SELinux. The client curl commands work on any Linux host with curl installed. The server terminates TLS, accepts WebDAV PUT on /web/, and requires a username and password before httpd starts listening. The client verifies the server certificate with --cacert, then uploads and downloads with curl. If you already transfer files with scp or rsync over SSH, use this pattern when port 443 and a web endpoint are the required path.


Quick answer

Goal Command (run on the client)
Upload with verified TLS and auth curl --cacert ~/vm1.lab.example.crt -u USER -T /path/to/local.file https://vm1.lab.example/web/local.file
Download with verified TLS and auth curl --cacert ~/vm1.lab.example.crt -u USER -o /path/to/save https://vm1.lab.example/web/remote.file
Public CA certificate (no extra TLS flag) curl -u USER -T ... https://SERVER/web/... when the system CA store already trusts the server
Lab-only: skip TLS verification curl -k -u USER -T ... — encrypts traffic but does not verify the server

Pass only the username to -u; curl prompts for the password instead of reading it from the command line. Use the hostname that matches the certificate (vm1.lab.example here), not the bare IP, when you pass --cacert.


How the HTTPS file transfer works

Three layers stack on top of each other:

  1. Transport — TLS on port 443 encrypts the connection. curl must trust the server certificate (--cacert, system CA store, or a public CA) and the URL hostname must match the certificate name.
  2. Protocolcurl -T sends an HTTP PUT request. Apache needs WebDAV (Dav On) on /web/ so PUT creates or replaces files on disk.
  3. Access control — HTTP basic authentication limits who can read or write /web/. Pass -u username and enter the password when curl prompts.

Store WebDAV files outside Apache's normal DocumentRoot so /web/ is the only intended HTTP entry point. Files are not also reachable under https://SERVER/secret/ or another default site path.

TLS approach curl flags Typical use
Public CA (none — default) Production sites with Let's Encrypt or another public CA
Private CA or self-signed server --cacert /path/to/ca-or-trusted-cert.pem Internal lab, corporate CA, or a self-signed server certificate copied to the client
System trust store Install cert, then plain curl Many scripts on one host calling the same internal server
Skip verification -k Isolated debugging only

Mutual TLS (--cert and --key on the client) is a separate pattern used when the server requires a client certificate during the TLS handshake. This setup uses TLS verification plus HTTP basic auth instead.


Install Apache and WebDAV

On RHEL, Rocky Linux, AlmaLinux, or Fedora, install httpd, mod_ssl, httpd-tools (for htpasswd), openssl, and policycoreutils-python-utils (needed for semanage on SELinux systems). WebDAV modules ship with httpd and load from /etc/httpd/conf.modules.d/00-dav.conf.

bash
dnf install -y httpd mod_ssl httpd-tools openssl policycoreutils-python-utils

Confirm mod_ssl and the DAV modules are loaded:

bash
httpd -M | grep -E 'ssl_module|dav_module|dav_fs_module'

Sample output:

output
dav_module (shared)
 dav_fs_module (shared)
 ssl_module (shared)

dav_module provides WebDAV support, while dav_fs_module lets WebDAV store files on the local filesystem.

Filesystem WebDAV also needs a lock database. Create it and give apache ownership:

bash
mkdir -p /var/lib/dav
chown apache:apache /var/lib/dav

DavLockDB belongs at server configuration level, not inside <Location>.


Create the WebDAV directory

Put upload data outside /var/www/html so it is not also published as part of the default website tree:

bash
mkdir -p /var/www/webdav
echo 'download-me' > /var/www/webdav/sample.txt
chown apache:apache /var/www/webdav
chmod 775 /var/www/webdav

On RHEL with SELinux enforcing, label the directory so httpd can write uploaded files:

bash
semanage fcontext -a -t httpd_sys_rw_content_t "/var/www/webdav(/.*)?"
restorecon -Rv /var/www/webdav

If semanage reports the rule already exists, run only restorecon.


Create the TLS certificate

Generate a key and certificate for the hostname clients use in the URL. This example uses vm1.lab.example on the file server:

bash
openssl req -x509 -nodes -newkey rsa:2048 -days 365 \
  -keyout /etc/pki/tls/certs/vm1.lab.example.key \
  -out /etc/pki/tls/certs/vm1.lab.example.crt \
  -subj "/CN=vm1.lab.example" \
  -addext "subjectAltName=DNS:vm1.lab.example"
chmod 600 /etc/pki/tls/certs/vm1.lab.example.key

The certificate contains vm1.lab.example as a DNS name, so curl must use that hostname in the HTTPS URL.

Point Apache at those files. On RHEL, edit the two certificate directives in /etc/httpd/conf.d/ssl.conf:

text
SSLCertificateFile /etc/pki/tls/certs/vm1.lab.example.crt
SSLCertificateKeyFile /etc/pki/tls/certs/vm1.lab.example.key

Replace the default localhost.crt paths if they are still referenced there. Do not start httpd yet; create the WebDAV user and authenticated configuration first.


Create the WebDAV user

Create a password file and user filetransfer on the file server:

bash
htpasswd -c /etc/httpd/webdav.htpasswd filetransfer

htpasswd prompts for the password twice instead of reading it from the command line:

output
New password:
Re-type new password:
Adding password for user filetransfer

Restrict permissions so only root and apache can read the file:

bash
chmod 640 /etc/httpd/webdav.htpasswd
chown root:apache /etc/httpd/webdav.htpasswd

Configure authenticated WebDAV

Map /web/ to the directory outside DocumentRoot and require basic auth before you start Apache:

bash
cat > /etc/httpd/conf.d/file-transfer-webdav.conf <<'EOF'
DavLockDB /var/lib/dav/DavLock

Alias /web/ /var/www/webdav/

<Directory /var/www/webdav>
    Options Indexes
    AllowOverride None
    AuthType Basic
    AuthName "HTTPS file transfer"
    AuthUserFile /etc/httpd/webdav.htpasswd
    Require valid-user
</Directory>

<Location /web/>
    Dav On
</Location>
EOF

Dav On is what allows curl -T (HTTP PUT). Require valid-user means no unauthenticated read or write access to /web/.


Start Apache

Validate the full configuration:

bash
apachectl configtest

Sample output:

output
Syntax OK

Enable and start the service:

bash
systemctl enable --now httpd

Open HTTPS in the firewall when firewalld is active:

bash
firewall-cmd --permanent --add-service=https
firewall-cmd --reload

Confirm httpd is listening on 443:

bash
ss -tlnp | grep ':443'

Sample output:

output
LISTEN 0 511 *:443 *:* users:(("httpd",pid=18011,fd=6))

Trust the certificate on the client

--cacert tells curl which PEM file to use when verifying the server. Copy that file to the client through a channel you already trust, such as your configuration management system, an internal PKI portal, or SSH if you have it. Do not download the trust material over an unverified HTTPS connection; that would undermine the bootstrap step.

If SSH access to the file server is available, copy the certificate into your home directory on the client:

bash
scp admin@192.168.56.116:/etc/pki/tls/certs/vm1.lab.example.crt ~/vm1.lab.example.crt

The account must be able to read the PEM file on the server, or an administrator must place a copy in a path you can reach.

If DNS does not already resolve vm1.lab.example, add a hosts entry once. That step requires sudo on the client:

bash
grep -q 'vm1.lab.example' /etc/hosts || echo '192.168.56.116 vm1.lab.example' | sudo tee -a /etc/hosts

Test TLS verification with a read-only GET before you upload anything. Without a trusted certificate, curl rejects the self-signed server cert:

bash
curl https://vm1.lab.example/web/sample.txt

Sample output:

output
curl: (60) SSL certificate problem: self-signed certificate
More details here: https://curl.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it.

Point curl at the copied certificate and supply credentials:

bash
curl --cacert ~/vm1.lab.example.crt -u filetransfer https://vm1.lab.example/web/sample.txt

Enter the WebDAV password when prompted. Sample output:

output
download-me

That confirms both TLS verification and basic authentication work before you run an upload.

A request with --cacert but without -u should return 401:

bash
curl -sS -o /dev/null -w '%{http_code}\n' --cacert ~/vm1.lab.example.crt https://vm1.lab.example/web/sample.txt

Sample output:

output
401

When many scripts on one host call the same internal server, you can install the certificate in the system trust store so plain curl works for every user. That requires root or sudo on the client. On RHEL and Fedora, as root:

bash
cp ~/vm1.lab.example.crt /etc/pki/ca-trust/source/anchors/vm1.lab.example.crt

Rebuild the bundle:

bash
update-ca-trust extract

After that, curl against https://vm1.lab.example/... verifies the server without --cacert on RHEL and Fedora clients.


Upload a file securely with curl

On the client, create a local file and upload it with verified TLS and basic auth:

bash
echo 'upload-from-client' > /tmp/client_upload.txt
curl --cacert ~/vm1.lab.example.crt -u filetransfer -T /tmp/client_upload.txt https://vm1.lab.example/web/client_upload.txt

-T (--upload-file) sends the file with HTTP PUT. --cacert verifies the server certificate. -u filetransfer makes curl prompt with Enter host password for user 'filetransfer': instead of putting the password on the command line, where other users could see it in a process listing.

Sample output after you enter the password:

output
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>201 Created</title>
</head><body>
<h1>Created</h1>
<p>Resource /web/client_upload.txt has been created.</p>
</body></html>

Because client_upload.txt did not already exist, 201 Created means Apache created the new resource successfully. Replacing an existing file may return a different successful status such as 204 No Content.

On the file server, confirm the uploaded content:

bash
cat /var/www/webdav/client_upload.txt

Sample output:

output
upload-from-client

Seeing the expected content confirms that the file uploaded successfully.


Download a file securely with curl

Download the sample file with the same certificate trust and credentials:

bash
curl --cacert ~/vm1.lab.example.crt -u filetransfer -o /tmp/downloaded.txt https://vm1.lab.example/web/sample.txt

-o writes the response body to /tmp/downloaded.txt instead of printing it to the terminal. Enter the WebDAV password when curl prompts for it.

Check the saved file:

bash
cat /tmp/downloaded.txt

Sample output:

output
download-me

For more curl flags and HTTP methods, see the curl command cheat sheet.


Troubleshooting

Symptom Likely cause Fix
curl: (7) Failed to connect Firewall or httpd not listening on 443 Run systemctl status httpd; open https in firewalld; verify with ss -tlnp | grep 443
401 Unauthorized Missing or wrong credentials Pass -u filetransfer and enter the password at the prompt; confirm the user exists in webdav.htpasswd
SSL certificate problem: self-signed certificate Client does not trust the server cert Copy the PEM to ~/ and use --cacert, or install it in the system trust store as root
certificate subject name ... does not match target hostname URL uses an IP but the cert names a hostname Use https://vm1.lab.example/... or reissue the cert with the IP in SAN
500 Internal Server Error on PUT Directory not writable by apache, missing DavLockDB, or SELinux block Set DavLockDB and chown apache:apache /var/lib/dav; chown apache:apache /var/www/webdav; apply httpd_sys_rw_content_t with semanage and restorecon
405 Method Not Allowed WebDAV not enabled on /web/ Confirm Dav On inside <Location /web/> and restart httpd
Files reachable at two URLs WebDAV data stored under DocumentRoot Move uploads to a directory outside /var/www/html and map only /web/ with Alias
CONNECT tunnel failed or unexpected proxy errors https_proxy set in the shell Run curl --noproxy '*' ... or unset proxy variables for direct HTTPS to the file server
Upload works only with -k TLS verification skipped but hostname or trust still wrong without it Fix --cacert path and use the certificate hostname in the URL

References


Summary

Secure HTTPS file transfer stacks TLS encryption, certificate verification, and access control on an RHEL-family file server. You install Apache with mod_ssl and WebDAV, store uploads in /var/www/webdav outside DocumentRoot, map only /web/ with basic auth configured before httpd starts, and copy the server PEM to ~/vm1.lab.example.crt on the client through a trusted channel for curl --cacert.

Uploads use curl -T with -u username; downloads use curl -o with the same flags. Enter the password when prompted rather than on the command line. Both commands target https://vm1.lab.example so the hostname matches the certificate. Test TLS with a read-only GET before you upload. A 401 response means credentials are missing; an SSL error usually means --cacert points at the wrong file or the URL uses an IP instead of the certificate hostname.

On a host with a public CA certificate, plain curl is enough for TLS. Internal servers need --cacert with a CA or trusted server PEM, or a private CA in the system trust store. Reserve -k for lab debugging. For day-to-day server-to-server copies without a web front door, SSH-based tools remain simpler; use this pattern when HTTPS on port 443 is the required transport.


Frequently Asked Questions

1. Why use curl -T instead of scp for file transfer?

scp and rsync over SSH are the usual choice between servers you already manage with SSH keys. HTTPS upload with curl -T fits when a web endpoint or load balancer front door already accepts PUT, or when you must use port 443 through a restrictive firewall.

2. How do I verify a self-signed certificate with curl?

Copy the trusted PEM file to the client through a channel you already trust, then pass it with --cacert /path/to/ca-or-trusted-cert.pem. Use the hostname that appears as a DNS name in the certificate in the URL, not the raw IP address.

3. When is curl -k acceptable?

Use -k or --insecure only in isolated lab debugging. It still encrypts traffic but disables certificate and hostname checks, which removes protection against man-in-the-middle attacks. Prefer --cacert, a private CA, or a public CA-signed certificate in every environment you care about.

4. Do I need WebDAV to upload with curl?

curl -T sends an HTTP PUT request. The server must accept PUT on the target URL. Apache needs mod_dav enabled on that location. A plain static file directory without WebDAV returns an error on PUT.

5. What is the difference between --cacert and --cert in curl?

--cacert tells curl which CA or server certificate to trust when verifying the remote HTTPS host. --cert and --key present a client certificate for mutual TLS when the server requires client authentication. They solve different problems.

6. Why does curl return 401 Unauthorized on upload?

Apache is enforcing HTTP basic authentication on /web/. Pass -u username and enter the password when curl prompts, rather than putting the password on the command line. TLS verification with --cacert is separate from application-level auth.
Omer Cakmak

Linux Administrator

Highly skilled at managing Debian, Ubuntu, CentOS, Oracle Linux, and Red Hat servers. Proficient in bash scripting, Ansible, and AWX central server management, he handles server operations on OpenStack, KVM, Proxmox, and VMware.

  • Debian
  • Ubuntu
  • Linux
  • Red Hat Enterprise Linux
  • Shell Script
  • System Administration