[GH-ISSUE #4621] Need to make it clear: do I need to copy Let's Encrypt certificates every 3 months to get TLS to work for BOTH the server and the client? #3648

Closed
opened 2026-05-05 14:20:30 -06:00 by gitea-mirror · 8 comments
Owner

Originally created by @Kenya-West on GitHub (Jan 5, 2025).
Original GitHub issue: https://github.com/fatedier/frp/issues/4621

Describe the feature request

I have a setup like this:

# For actual proxying using TCP connections without TLS
some web service <- frpc <- [token auth] <- frps 

# For seeing stats in browser securely with SSL
frps web dashboard <- Caddy 

Configs are (templated by Jinja2):

Details

frps.toml:

bindPort = {{ docker_service_vars.frp.server_port }}
allowPorts = [
  { start = {{ docker_service_vars.frp.range_ports_1_start }}, end = {{ docker_service_vars.frp.range_ports_1_end }} }
]
webServer.addr = "0.0.0.0"
webServer.port = {{ docker_service_vars.frp.dashboard_port }}
webServer.user = "{{ docker_service_vars.frp.dashboard_user }}"
webServer.password = "{{ docker_service_vars.frp.dashboard_password }}"
auth.method = "{{ docker_service_vars.frp.auth_method }}"
auth.token = "{{ docker_service_vars.frp.auth_token }}"

frpc.toml:

webServer.addr = "0.0.0.0"
webServer.port = {{ proxy_client.frpc.client_dashboard_port }}
webServer.user = "{{ proxy_client.frpc.client_dashboard_user }}"
webServer.password = "{{ proxy_client.frpc.client_dashboard_password }}"
serverAddr = "{{ proxy_client.frpc.server_address }}"
serverPort = {{ proxy_client.frpc.server_port }}
auth.method = "{{ proxy_client.frpc.client_auth_method }}"
auth.token = "{{ proxy_client.frpc.client_auth_token }}"

{% for proxy in proxy_client.frpc.proxies %}

[[proxies]]
name = "{{ proxy.name | default(inventory_hostname) | default(ansible_host) | default('ssh_proxy') }}"
type = "{{ proxy.type | default('tcp') }}"
localIP = "{{ proxy.local_ip | default('127.0.0.1') }}"
localPort = {{ proxy.local_port }}
remotePort = {{ proxy.remote_port }}
{% endfor %}

So far so good without TLS. But I want to switch to TLS-only connections.

Caddy updates certificates on time and (as for now) just proxies for FRPS dashboard with SSL.
The actual frps<---[tcp]--->frpc connections FRPS does by itself without any TLS.

If I switch to SSL/TLS only, I need to copy Let's Encrypt certs from Caddy to both FRPS and FRPC, right? Or do I need to set certificates only in frps.toml, and the client will automatically switch to TLS?

Making certificates accessible for one server is OK. But to distribute certs for all the clients on regular basis adds unnecessary complexity to an already complicated infrastructure.

Describe alternatives you've considered

Alternative is to sit still and being happy without TLS

Affected area

  • Docs
  • Installation
  • Performance and Scalability
  • Security
  • User Experience
  • Test and Release
  • Developer Infrastructure
  • Client Plugin
  • Server Plugin
  • Extensions
  • Others
Originally created by @Kenya-West on GitHub (Jan 5, 2025). Original GitHub issue: https://github.com/fatedier/frp/issues/4621 ### Describe the feature request I have a setup like this: ``` # For actual proxying using TCP connections without TLS some web service <- frpc <- [token auth] <- frps # For seeing stats in browser securely with SSL frps web dashboard <- Caddy ``` Configs are (templated by Jinja2): <details><summary>Details</summary> <p> **frps.toml**: ```toml bindPort = {{ docker_service_vars.frp.server_port }} allowPorts = [ { start = {{ docker_service_vars.frp.range_ports_1_start }}, end = {{ docker_service_vars.frp.range_ports_1_end }} } ] webServer.addr = "0.0.0.0" webServer.port = {{ docker_service_vars.frp.dashboard_port }} webServer.user = "{{ docker_service_vars.frp.dashboard_user }}" webServer.password = "{{ docker_service_vars.frp.dashboard_password }}" auth.method = "{{ docker_service_vars.frp.auth_method }}" auth.token = "{{ docker_service_vars.frp.auth_token }}" ``` **frpc.toml**: ```toml webServer.addr = "0.0.0.0" webServer.port = {{ proxy_client.frpc.client_dashboard_port }} webServer.user = "{{ proxy_client.frpc.client_dashboard_user }}" webServer.password = "{{ proxy_client.frpc.client_dashboard_password }}" serverAddr = "{{ proxy_client.frpc.server_address }}" serverPort = {{ proxy_client.frpc.server_port }} auth.method = "{{ proxy_client.frpc.client_auth_method }}" auth.token = "{{ proxy_client.frpc.client_auth_token }}" {% for proxy in proxy_client.frpc.proxies %} [[proxies]] name = "{{ proxy.name | default(inventory_hostname) | default(ansible_host) | default('ssh_proxy') }}" type = "{{ proxy.type | default('tcp') }}" localIP = "{{ proxy.local_ip | default('127.0.0.1') }}" localPort = {{ proxy.local_port }} remotePort = {{ proxy.remote_port }} {% endfor %} ``` </p> </details> So far so good without TLS. But I want to switch to TLS-only connections. Caddy updates certificates on time and (as for now) just proxies for FRPS dashboard with SSL. The actual `frps<---[tcp]--->frpc` connections FRPS does by itself without any TLS. If I switch to SSL/TLS only, I need to copy Let's Encrypt certs from **Caddy** to both **FRPS** and **FRPC**, right? Or do I need to set certificates only in `frps.toml`, and the client will automatically switch to TLS? Making certificates accessible for one server is OK. But to distribute certs for all the clients on regular basis adds unnecessary complexity to an already complicated infrastructure. ### Describe alternatives you've considered Alternative is to sit still and being happy without TLS ### Affected area - [ ] Docs - [ ] Installation - [ ] Performance and Scalability - [X] Security - [ ] User Experience - [ ] Test and Release - [ ] Developer Infrastructure - [ ] Client Plugin - [ ] Server Plugin - [ ] Extensions - [ ] Others
Author
Owner

@fatedier commented on GitHub (Jan 6, 2025):

By default, traffic between frpc and frps is encrypted using randomly generated certificates. You only need to provide your own certificates if you require additional authentication. Whether you need to update certificates on frpc depends on whether you require mutual TLS (mTLS) for bidirectional authentication. Without mTLS, it’s sufficient to configure certificates only on frps. This simplifies the setup, as you don't need to distribute certificates to all clients unless mutual authentication is a strict requirement in your environment.

<!-- gh-comment-id:2572210917 --> @fatedier commented on GitHub (Jan 6, 2025): By default, traffic between frpc and frps is encrypted using randomly generated certificates. You only need to provide your own certificates if you require additional authentication. Whether you need to update certificates on frpc depends on whether you require mutual TLS (mTLS) for bidirectional authentication. Without mTLS, it’s sufficient to configure certificates only on frps. This simplifies the setup, as you don't need to distribute certificates to all clients unless mutual authentication is a strict requirement in your environment.
Author
Owner

@Kenya-West commented on GitHub (Jan 7, 2025):

Thanks for trying to help. But maybe I simplified the problem too much.

I try to explain again (sorry for possible misunderstanding before):

I setup a web service on my laptop, that serves only by HTTP. It is restic REST backup server if you need to know.

I need FRP to reverse proxy to this HTTP service only by HTTPS. Can FRP do this?

Currently, I have such configs (more detailed):

frpc.tml:

Details

webServer.addr = "0.0.0.0"
webServer.port = 7400
webServer.user = "user"
webServer.password = "password"
serverAddr = "subdomain.example.tld"
serverPort = 7000
auth.method = "token"
auth.token = "sometoken"
transport.tls.enable = true
transport.tls.certFile = "/etc/frp/certs/subdomain.example.tld/subdomain.example.tld.crt"
transport.tls.keyFile = "/etc/frp/certs/subdomain.example.tld/subdomain.example.tld.key"
transport.tls.serverName = "subdomain.example.tld"

[[proxies]]
name = "mylaptop"
type = "tcp"
localIP = "127.0.0.1"
localPort = 23001
remotePort = 22001

[[proxies]]
name = "mylaptop_restic_server"
type = "tcp"
localIP = "127.0.0.1"
localPort = 8010
remotePort = 22002

frps.toml:

Details

bindPort = 7000
vhostHTTPPort = 8080
vhostHTTPSPort = 8443
allowPorts = [
  { start = 22001, end = 22089 }
]
webServer.addr = "0.0.0.0"
webServer.port = 7500
webServer.user = "user"
webServer.password = "password"
auth.method = "token"
auth.token = "sometoken"
transport.tls.force = true
transport.tls.certFile = "/etc/frp/certs/subdomain.example.tld/subdomain.example.tld.crt"
transport.tls.keyFile = "/etc/frp/certs/subdomain.example.tld/subdomain.example.tld.key"

Right now, it gets error from restic that HTTP response is sent through HTTPS connection:

Details

$kenyawest: ~/backup-restic-node/autorestic ❯ sudo scripts/update.sh
Using config:    /home/kenyawest/backup-restic-node/autorestic/config/.autorestic.yaml
Using env:       /home/kenyawest/backup-restic-node/autorestic/config/.autorestic.env
Using lock:      /home/kenyawest/backup-restic-node/autorestic/config/.autorestic.lock.yml


    Backing up location "home_user"

Backend: server
> Executing: /usr/local/bin/restic backup --tag ar:location:home_user /home/kenyawest
Fatal: unable to open config file: Head "https://user:***@subdomain.example.tld:22002/config": http: server gave HTTP response to HTTPS client
Is there a repository at the following location?
rest:https://user:***@subdomain.example.tld:22002/

home_user@server:
Fatal: unable to open config file: Head "https://user:***@subdomain.example.tld:22002/config": http: server gave HTTP response to HTTPS client
Is there a repository at the following location?
rest:https://user:***@subdomain.example.tld:22002/
exit status 1

Error: 1 errors were found

The documentation has a paragraphs about my exact specific use case:

Details

Enable HTTPS for a local HTTP(S) service

You may substitute https2https for the plugin, and point the localAddr to a HTTPS endpoint.

  1. Start frpc with the following configuration:
# frpc.toml
serverAddr = "x.x.x.x"
serverPort = 7000

[[proxies]]
name = "test_https2http"
type = "https"
customDomains = ["test.example.com"]

[proxies.plugin]
type = "https2http"
localAddr = "127.0.0.1:80"
crtPath = "./server.crt"
keyPath = "./server.key"
hostHeaderRewrite = "127.0.0.1"
requestHeaders.set.x-from-where = "frp"
  1. Visit https://test.example.com.

But even with the same certificates both on client and server, FRP cannot wrap/transform/remap HTTP traffic to HTTPS one.

Does that mean it is not FRP's job?

<!-- gh-comment-id:2575301328 --> @Kenya-West commented on GitHub (Jan 7, 2025): Thanks for trying to help. But maybe I simplified the problem too much. ### **I try to explain again** (sorry for possible misunderstanding before): I setup a web service on my laptop, that serves only by HTTP. It is `restic` REST backup server if you need to know. **I need FRP to reverse proxy to this HTTP service only by HTTPS**. Can FRP do this? Currently, I have such configs (more detailed): `frpc.tml`: <details><summary>Details</summary> <p> ```toml webServer.addr = "0.0.0.0" webServer.port = 7400 webServer.user = "user" webServer.password = "password" serverAddr = "subdomain.example.tld" serverPort = 7000 auth.method = "token" auth.token = "sometoken" transport.tls.enable = true transport.tls.certFile = "/etc/frp/certs/subdomain.example.tld/subdomain.example.tld.crt" transport.tls.keyFile = "/etc/frp/certs/subdomain.example.tld/subdomain.example.tld.key" transport.tls.serverName = "subdomain.example.tld" [[proxies]] name = "mylaptop" type = "tcp" localIP = "127.0.0.1" localPort = 23001 remotePort = 22001 [[proxies]] name = "mylaptop_restic_server" type = "tcp" localIP = "127.0.0.1" localPort = 8010 remotePort = 22002 ``` </p> </details> `frps.toml`: <details><summary>Details</summary> <p> ```toml bindPort = 7000 vhostHTTPPort = 8080 vhostHTTPSPort = 8443 allowPorts = [ { start = 22001, end = 22089 } ] webServer.addr = "0.0.0.0" webServer.port = 7500 webServer.user = "user" webServer.password = "password" auth.method = "token" auth.token = "sometoken" transport.tls.force = true transport.tls.certFile = "/etc/frp/certs/subdomain.example.tld/subdomain.example.tld.crt" transport.tls.keyFile = "/etc/frp/certs/subdomain.example.tld/subdomain.example.tld.key" ``` </p> </details> Right now, it gets error from `restic` that HTTP response is sent through HTTPS connection: <details><summary>Details</summary> <p> ```sh  $kenyawest: ~/backup-restic-node/autorestic ❯ sudo scripts/update.sh Using config: /home/kenyawest/backup-restic-node/autorestic/config/.autorestic.yaml Using env: /home/kenyawest/backup-restic-node/autorestic/config/.autorestic.env Using lock: /home/kenyawest/backup-restic-node/autorestic/config/.autorestic.lock.yml Backing up location "home_user" Backend: server > Executing: /usr/local/bin/restic backup --tag ar:location:home_user /home/kenyawest Fatal: unable to open config file: Head "https://user:***@subdomain.example.tld:22002/config": http: server gave HTTP response to HTTPS client Is there a repository at the following location? rest:https://user:***@subdomain.example.tld:22002/ home_user@server: Fatal: unable to open config file: Head "https://user:***@subdomain.example.tld:22002/config": http: server gave HTTP response to HTTPS client Is there a repository at the following location? rest:https://user:***@subdomain.example.tld:22002/ exit status 1 Error: 1 errors were found ``` </p> </details> The documentation has a paragraphs about my exact specific use case: <details><summary>Details</summary> <p> ### Enable HTTPS for a local HTTP(S) service You may substitute `https2https` for the plugin, and point the `localAddr` to a HTTPS endpoint. 1. Start `frpc` with the following configuration: ```toml # frpc.toml serverAddr = "x.x.x.x" serverPort = 7000 [[proxies]] name = "test_https2http" type = "https" customDomains = ["test.example.com"] [proxies.plugin] type = "https2http" localAddr = "127.0.0.1:80" crtPath = "./server.crt" keyPath = "./server.key" hostHeaderRewrite = "127.0.0.1" requestHeaders.set.x-from-where = "frp" ``` 2. Visit `https://test.example.com`. </p> </details> But even with the same certificates both on client and server, FRP cannot wrap/transform/remap HTTP traffic to HTTPS one. Does that mean it is not FRP's job?
Author
Owner

@fatedier commented on GitHub (Jan 7, 2025):

Maybe try this: https://github.com/fatedier/frp?tab=readme-ov-file#enable-https-for-a-local-https-service

<!-- gh-comment-id:2575399235 --> @fatedier commented on GitHub (Jan 7, 2025): Maybe try this: https://github.com/fatedier/frp?tab=readme-ov-file#enable-https-for-a-local-https-service
Author
Owner

@Kenya-West commented on GitHub (Jan 9, 2025):

Maybe try this: https://github.com/fatedier/frp?tab=readme-ov-file#enable-https-for-a-local-https-service

Thanks. Sorry for asking you like it is tech support. But you have more experience in this. I will be glad if you look at this.

I think I setup it correctly but the service still not available:

frpc.toml

webServer.addr = "0.0.0.0"
webServer.port = 7400
webServer.user = "user"
webServer.password = "password"
serverAddr = "sub.example.tld"
serverPort = 7000
auth.method = "token"
auth.token = "sometoken"


[[proxies]]
name = "kw-ubuntu"
type = "tcp"
localPort = 23001
remotePort = 22001

[[proxies]]
name = "kw-ubuntu_restic_server"
type = "https"
customDomains = ["sub.example.tld"]

[proxies.plugin]
type = "https2http"
localAddr = "127.0.0.1:8010" # web service listening on this port (restic, to be precise)
crtPath = "/etc/frp/certs/sub.example.tld/sub.example.tld.crt"
keyPath = "/etc/frp/certs/sub.example.tld/sub.example.tld.key"
hostHeaderRewrite = "127.0.0.1"
requestHeaders.set.x-from-where = "frp"

frps.toml

bindPort = 7000
vhostHTTPPort = 8080
vhostHTTPSPort = 8443
allowPorts = [
  { start = 22001, end = 22089 }
]
webServer.addr = "0.0.0.0"
webServer.port = 7500
webServer.user = "user"
webServer.password = "password"
auth.method = "token"
auth.token = "sometoken"

And looks like it is setup correctly according to logs and FRPS dashboard:

image

But the connection is still not happening:

$kenyawest: ~ ❯ curl https://sub.example.tld:8443 -v
*   Trying 91.108.243.8:8443...
* connect to 91.108.243.8 port 8443 failed: Connection refused
* Failed to connect to sub.example.tld port 8443 after 130 ms: Connection refused
* Closing connection 0

Locally, it works, web service replies correctly. UFW is disabled or properly setup on both client and server.

How do I debug this?

<!-- gh-comment-id:2579979092 --> @Kenya-West commented on GitHub (Jan 9, 2025): > Maybe try this: https://github.com/fatedier/frp?tab=readme-ov-file#enable-https-for-a-local-https-service Thanks. Sorry for asking you like it is tech support. But you have more experience in this. I will be glad if you look at this. I think I setup it correctly but the service still not available: <details><summary>frpc.toml</summary> <p> ```toml webServer.addr = "0.0.0.0" webServer.port = 7400 webServer.user = "user" webServer.password = "password" serverAddr = "sub.example.tld" serverPort = 7000 auth.method = "token" auth.token = "sometoken" [[proxies]] name = "kw-ubuntu" type = "tcp" localPort = 23001 remotePort = 22001 [[proxies]] name = "kw-ubuntu_restic_server" type = "https" customDomains = ["sub.example.tld"] [proxies.plugin] type = "https2http" localAddr = "127.0.0.1:8010" # web service listening on this port (restic, to be precise) crtPath = "/etc/frp/certs/sub.example.tld/sub.example.tld.crt" keyPath = "/etc/frp/certs/sub.example.tld/sub.example.tld.key" hostHeaderRewrite = "127.0.0.1" requestHeaders.set.x-from-where = "frp" ``` </p> </details> <details><summary>frps.toml</summary> <p> ```toml bindPort = 7000 vhostHTTPPort = 8080 vhostHTTPSPort = 8443 allowPorts = [ { start = 22001, end = 22089 } ] webServer.addr = "0.0.0.0" webServer.port = 7500 webServer.user = "user" webServer.password = "password" auth.method = "token" auth.token = "sometoken" ``` </p> </details> And looks like it is setup correctly according to logs and FRPS dashboard: ![image](https://github.com/user-attachments/assets/b7a9a01f-6461-4a4b-b0a5-7ca0d7bbfcc0) But the connection is still not happening: ```sh  $kenyawest: ~ ❯ curl https://sub.example.tld:8443 -v * Trying 91.108.243.8:8443... * connect to 91.108.243.8 port 8443 failed: Connection refused * Failed to connect to sub.example.tld port 8443 after 130 ms: Connection refused * Closing connection 0 ``` Locally, it works, web service replies correctly. UFW is disabled or properly setup on both client and server. How do I debug this?
Author
Owner

@fatedier commented on GitHub (Jan 9, 2025):

You should check whether the server-side port is properly open, provide necessary logs, and perform preliminary troubleshooting to save time.

<!-- gh-comment-id:2580007163 --> @fatedier commented on GitHub (Jan 9, 2025): You should check whether the server-side port is properly open, provide necessary logs, and perform preliminary troubleshooting to save time.
Author
Owner

@Kenya-West commented on GitHub (Jan 9, 2025):

You should check whether the server-side port is properly open, provide necessary logs, and perform preliminary troubleshooting to save time.

You were right, I forgot to double-check docker-compose.yml file and open vhostHTTPSPort. Now it is proxied correctly and works as expected. It is now working correctly 🍾!

But I still have some architectural questions regarding this setup:

  1. This setup (I mean https://github.com/fatedier/frp?tab=readme-ov-file#enable-https-for-a-local-https-service) means that certificates should be stored on client side. In my case, it requires complex maintenance to properly update these certificates every 3 months or more often so they do not expire.
    I am not so good in networks. Is there any way to avoid storing Let's Encrypt certificates on client? Your thoughts?

  2. I exposed 8443 port to serve only one web service to HTTPS. Since this port is single, how do I expose multiple local web services to a single port? Using NGINX or Caddy, right? This use case is not covered by FRP and FRPS has no tools to do this, right?

<!-- gh-comment-id:2580076333 --> @Kenya-West commented on GitHub (Jan 9, 2025): > You should check whether the server-side port is properly open, provide necessary logs, and perform preliminary troubleshooting to save time. You were right, I forgot to double-check `docker-compose.yml` file and open `vhostHTTPSPort`. Now it is proxied correctly and works as expected. It is now working correctly 🍾! But I still have some architectural questions regarding this setup: 1. This setup (I mean https://github.com/fatedier/frp?tab=readme-ov-file#enable-https-for-a-local-https-service) means that certificates should be stored on client side. In my case, it requires complex maintenance to properly update these certificates every 3 months or more often so they do not expire. I am not so good in networks. Is there any way to avoid storing Let's Encrypt certificates on client? Your thoughts? 2. I exposed `8443` port to serve only one web service to HTTPS. Since this port is single, how do I expose multiple local web services to a single port? Using NGINX or Caddy, right? This use case is not covered by FRP and FRPS has no tools to do this, right?
Author
Owner

@fatedier commented on GitHub (Jan 9, 2025):

  1. There is currently no solution.
  2. HTTPS-type proxies are routed by custom domains.
<!-- gh-comment-id:2580082086 --> @fatedier commented on GitHub (Jan 9, 2025): 1. There is currently no solution. 2. HTTPS-type proxies are routed by custom domains.
Author
Owner

@Kenya-West commented on GitHub (Jan 9, 2025):

Looks like I am starting to understand this wonderful world of proxying. Thanks for rapid responses, and thanks for developing FRP project! I will continue to discover things there.

Since this issue is resolved, I close it because there nothing to discuss regarding this topic.

<!-- gh-comment-id:2580095496 --> @Kenya-West commented on GitHub (Jan 9, 2025): Looks like I am starting to understand this wonderful world of proxying. Thanks for rapid responses, and thanks for developing FRP project! I will continue to discover things there. Since this issue is resolved, I close it because there nothing to discuss regarding this topic.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: github-starred/frp#3648
No description provided.