Convert nginx to Caddy config

1. Caddy version (caddy version):


2. How I run Caddy:

Docker, nextcloud etc are working fine. So I only looking for help to understand on my own how rewrite and convert configurations in Caddy v2 from nginx to caddyfile.
For now I have: {
	encode gzip

	header * {
		# You may want some other header options...
		X-Frame-Options "DENY"
		X-XSS-Protection "1; mode=block"
		X-Content-Type-Options "nosniff"
		X-Frame-Options "SAMEORIGIN"

	root * /publichtml

	php_fastcgi myphp8:9000 {
		root /var/www/html
		index index.php

	# .htaccess / data / config / ... shouldn't be accessible from outside
	@forbidden {
		path /.htaccess
		path /.github/*
		path /config/*
		path /
		path /test.php
	respond @forbidden "Access denied" 403 {

	basicauth {
		my CREDS

3. The problem I’m having:

I want to learn how to convert this configuration from nginx to caddy and be sure this configuration is secure. Could you describe for me best practices and on what I need be aware? Also I will be very happy if you could give me examples 1:1 conversion from nginx.

Should I also convert apache htaccess files to caddy config? Or nginx conf is enought?


server {
	listen 80;

	root /var/www/html/pico;

	index index.php;

	location ~ ^/((config|content|vendor|composer\.(json|lock|phar))(/|$)|(.+/)?\.(?!well-known(/|$))) {
		try_files /index.php$is_args$args =404;

	location ~ \.php$ {
		try_files $uri =404;

		fastcgi_pass unix:/var/run/php5-fpm.sock;
		fastcgi_index index.php;
		include fastcgi_params;

		# Let Pico know about available URL rewriting
		fastcgi_param PICO_URL_REWRITING 1;

	location / {
		try_files $uri $uri/ /index.php$is_args$args;


<IfModule mod_rewrite.c>
    RewriteEngine On
    # May be required to access sub directories
    #RewriteBase /

    # Deny access to internal dirs and files by passing the URL to Pico
    RewriteRule ^(config|content|vendor|CHANGELOG\.md|composer\.(json|lock|phar))(/|$) index.php [L]
    RewriteRule (^\.|/\.)(?!well-known(/|$)) index.php [L]

    # Enable URL rewriting
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^ index.php [L]

    <IfModule mod_env.c>
        # Let Pico know about available URL rewriting

# Prevent file browsing
Options -Indexes -MultiViews

4. What I already tried:

I am tring to understand and learn what syntax, matchers, declaration to use to achive the same config like in nginx or apache.

Which version, exactly? Please run caddy version to find the version. It matters.

This looks strange. Why are you setting a different root for php_fastcgi than outside? Usually, you’d just set the root directive to the location of your site’s webroot.

Also, setting index is redundant, that’s already the default.

From this, it seems like your webroot is the same place as your vendor, composer.json, etc. That’s dangerous and a bad idea.

Your webroot on a modern PHP app should be in a subdirectory from that location, such as a public directory, which would contain your index.php entrypoint.

Generally I recommend starting from any Nginx examples, and completely ignoring the Apache examples if possible. Caddy is more similar to Nginx than Apache. Since Apache uses .htaccess files, it gets quite complicated to correctly cover all the rules if there are many in the app being served.

I don’t think that regexp can be directly converted to Caddy though, because it uses a negative lookahead (the (?! part).

1 Like

Thank you for your time!

This looks strange. Why are you setting a different root for php_fastcgi than outside? Usually, you’d just set the root directive to the location of your site’s webroot.

It’s like this, because PHP-FPM is in another container(different stack) and it has mounted volume in different naming, my example below:
Volumen in Caddy stack:
Volume in PHP stack:

I would ask about this syntax which I found somewhere in web, is it valid? Can i use rewrite with {} or regex?:

rewrite {
  r /(data/|conf/|bin/|inc/|install.php)
  to /forbidden
rewrite {
    r ^config.php$
    to /forbidden.html #or any error path/page.
@content path_regexp content ^/content/([0-9a-zA-Z_]+)$
rewrite @content /bbs/content.php?co_id={re.content.1}&rewrite=1

From this, it seems like your webroot is the same place as your vendor , composer.json , etc. That’s dangerous and a bad idea.

Dangerous if you cannot properly setup nginx (or in my case Caddy). So this is why I am trying to understand this.

About structure of folders - it’s not my package I am only try to create Caddyfile for such structure.

Could you guide me a bit with rewriting examples. How to achive direct translation for such nginx files to put in Caddyfile? Standard things - like nginx location → you need use redir etc. because this will help me understand this better and on my own proceed with different web things etc :wink:

That’s actually not the latest. Please upgrade to v2.4.5!

You should definitely make sure those volumes are the same inside the containers. It simplifies things a lot.

No, that’s Caddy v1 syntax. Caddy v1 is not compatible with Caddy v2. The regexp itself should be compatible, but the rewrite directive works differently now, you need to use request matchers.

This is Caddy v2 syntax though, and that looks probably fine. Note that if the request had a query (part with ?) then it would be lost with that rewrite though. You could preserve the query by appending &{query} at the end of that rewrite.

That’s unfortunate. It’s so much easier to make sure things are secured by moving the webroot to a subdirectory. It’s a big reason why legacy projects are plagued with security issues. It’s extremely difficult to correctly handle all the possible crafted requests that could allow someone to bypass request matching and get at sensitive files. The safest way is to set the webroot to something deep enough that is completely shielded from the scope of sensitive files.

The file_server directive can ensure that request paths cannot escape from the configured root, but there’s very little guarantee of safety if you’re relying on request matchers you (or anyone else) has crafted to block requests.

1 Like

Thanks a lot for your time! I appreciate it!
Now it’s more clear for me. Version is bumped up now. Regarding web root issue, I will take steps to move this deeper. I will go with that example’s syntax and try to translate them.

If someone has ready to go examples 1:1 converted from nginx to Caddy v2 I will be glad for sharing.

This topic was automatically closed after 30 days. New replies are no longer allowed.