Rewrite rule not working with apache2, lucee tomcat

What happens if you open the file directly through tomcat, e.g. ?

Can you also check lucee access log files for and find out how these requests are being passed to Lucee? Because Tomcat throws that error, would be interresting to see how it sees the request.

It is working correctly. Showing the actual content of file.

If i try.
404 error return.

Nothing is showing in logs. Only following logs are initiated.

The access logs I’m talking about are Tomcats. They should be in /opt/lucee/tomcat/logs, but it depends on your Tomcat’s server.xml setting for that virtual host.

The ErrorDocument 404 directive in your .htaccess will affect static files only .jpg, .png, .html etc), not cfm files. If you access a non-existing jpg, it will return your /test-404.cfm page. This works similar as a URLrewrite and redirected to Tomcat because the test-404.cfm is a cfm file. But be aware that this will also return a 200 status then, unless you set a <cfheader statusCode="404" ...> in that test-404.cfm page.

The .htaccess ErrorDocument 404 directive will not affect 404 errors for .cfm requests because they will be passed to Tomcat directly, and then Tomcat will do the rest of the job. Tomcat then will decide how to handle that non-existing files. In a default Lucee installation Lucee will do that job and respond with a 404 status code and transmit it back to apache2. You may define 404 error handlings in Lucee Administrator “Error” settings or define it with cferror in your application.cfc. What is strange is, that in your case Tomcat is sending you a default Tomcat message. That shouldn’t happen in a Lucee default installation. Instead you should see a 404 exception with a similar output as:

But you are seeing that Tomcat default 404 page. That shouldn’t be happening. I wonder why.

@andreas, Nothing is being added there too when i hit both URLs. Also in lucee administrator, default error templates are defined.

I think, the tomcat is not considering this url as cfm file because it is not going in onMissingTemplates function in application.cfc for this above url. But When i hit any other missing file like then it goes to onMissingTemplates function of application.cfc.

Ahhhhh! Ok… so I think your servlet-mapping may be missing. Open your Tomcat web.xml at /opt/lucee/tomcat/conf/web.xml look for the following section:


I think you will need to add some url pattern there for example.cfm to work,
Something like:



Hmm. I have multiple these type of page URLs so i have defined like this.


But it is giving error for every page:

HTTP Status 404 – Not Found

Type Status Report

Description The origin server did not find a current representation for the target resource or is not willing to disclose that one exists.
Apache Tomcat/9.0.35

Follwoing is also not working.


Please lets go step by step. I don’t think that <url-pattern>/*.cfm/*</url-pattern> will work. If you launch a Tomcat instance with that double asterisk/wildcard pattern it will very probably cause an exception.

Does it even work with <url-pattern>/example.cfm/*</url-pattern>? Did you try it?


Yes. It is working.

So I have to apply like this for all URLs?


What will be the disadvantages of this? Can i add this url pattern site specifically?

Ok, I think the main problem is that every requested cfm page is directed to proxy pass. Your URLrewrites will always be ignored, unless you place it before the proxy pass in the apache2 conf. But hat would affect all of your sites. I’d reconsider thinking about how you handle this. Best would be to have URLs without .cfm file extension, redirect all to central page and handle this whole stuff from there. There are lots of possibities, however it depends on lot of things your setup might need to consider. Can’t really tell.

That might work in special scenarios, but I wouldn’t recommend it at all, because you are also letting Lucee Servlet Engine use static files as cfml files. Also files for other servlet containers would be catched also. In my opinion that would be to risky to go that path.


Hm. I don’t want to change the URLs due to SEO and marketing. Can i specify proxy pas for this in virtual host and then a global proxy pass for every other site?

This was working good on Windows hosting. I think, IIS was handling this through web.config

 <httpErrors errorMode="Detailed">
            <remove statusCode="404" subStatusCode="-1" />
            <error statusCode="404" prefixLanguageFilePath="" path="/test-404.cfm" responseMode="ExecuteURL" />

But now i moved to linux hosting with apache2

It’s just the way how proxy pass is configured by default, but you can change that by movin those proxy pass from apache2.conf to the virtual hosts that need cfml. I think that is a very common practice for scenarios where lots of different engines (e.g. PHP) are working in parallel on a per virtual host manner. If for example I’d host a 100% static site, and another second CFML site, I’d do that too.

Antoher solution would be to set this 404 URLrewirte rule just before that proxy pass. The urlRewirte should then have the host name as a condition.

@andreas. Let me try rewrite before the proxy pass. But if i seperate the proxy pass for every virtual host then i need to remove this from apache2.conf?

<IfModule mod_proxy.c>
	ProxyPreserveHost On
	ProxyPassMatch ^/(.+\.cf[cm])(/.*)?$$1$2
	ProxyPassMatch ^/(.+\.cfml)(/.*)?$$1$2
	# optional mappings
	#ProxyPassMatch ^/flex2gateway/(.*)$$1
	#ProxyPassMatch ^/messagebroker/(.*)$$1
	#ProxyPassMatch ^/flashservices/gateway(.*)$$1
	#ProxyPassMatch ^/openamf/gateway/(.*)$$1
	#ProxyPassMatch ^/rest/(.*)$$1
	ProxyPassReverse /

Then i need to add this in virtual host like this:

<VirtualHost *:80>
    DocumentRoot /var/www/html/

    <Directory /var/www/html/>
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted

    ErrorLog ${APACHE_LOG_DIR}/error_main_example.log
    CustomLog ${APACHE_LOG_DIR}/access_main_example.log combined
    DirectoryIndex index.cfm
    RewriteRule ^/scripts/(.*)$ /ds/scripts/$1 [L,PT]
    RewriteRule ^/webfm/(.*)$ /ds/webfm/$1 [L,PT]
    RewriteRule ^/webapi/(.*)$ /ds/webapi/$1 [L,PT]
    RewriteRule ^/webman/(.*)$ /ds/webman/$1 [L,PT]
    <IfModule mod_proxy.c>
        ProxyPreserveHost On
        ProxyPassMatch ^/(.+\.cf[cm])(/.*)?$$1$2
        ProxyPassMatch ^/(.+\.cfml)(/.*)?$$1$2
        # optional mappings
        #ProxyPassMatch ^/flex2gateway/(.*)$$1
        #ProxyPassMatch ^/messagebroker/(.*)$$1
        #ProxyPassMatch ^/flashservices/gateway(.*)$$1
        #ProxyPassMatch ^/openamf/gateway/(.*)$$1
        #ProxyPassMatch ^/rest/(.*)$$1
        ProxyPassReverse /

I think your solution above won’t work because mod_proxy will always preceed urlrewrite, unless you do proxy with urlrewrite. Sorry for my misleading post above. To make urlrewrite work and use proxy, you need to flag the rewrite rule with [P] (for proxy). Here is a working example that should work for you:

  1. Set everything in apache2.conf in mod_proxy.c as comment, just leaving ProxyPreserveHost and ProxyPassReverse, like so:
<IfModule mod_proxy.c>
        ProxyPreserveHost On
        #ProxyPassMatch ^/(.+\.cf[cm])(/.*)?$$1$2
        #ProxyPassMatch ^/(.+\.cfml)(/.*)?$$1$2
        # optional mappings
        #ProxyPassMatch ^/flex2gateway/(.*)$$1
        #ProxyPassMatch ^/messagebroker/(.*)$$1
        #ProxyPassMatch ^/flashservices/gateway(.*)$$1
        #ProxyPassMatch ^/openamf/gateway/(.*)$$1
        #ProxyPassMatch ^/rest/(.*)$$1
        ProxyPassReverse /
  1. In your virtual host configuration set the following rewrite rules ( that may work also in your .htaccess, but I’m not sure).

<Directory /var/www/html/>
RewriteEngine On
RewriteBase /

# Catch non-existing files/directories and pass them via proxy to a 404 cfml errorpage
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule "^(.*)$" "" [P]

# Catch
# Catch etc.
RewriteRule "^example.cfm(/abc/[0-9]+)$" "" [P]

# Pass request for cfm/cfc files to proxy with mod_rewrite rule
RewriteRule  "^(.+\.cf[cm])(/.*)?$" "$1$2"  [P]



I’ve tested it and the solution above works fine. For more information please refer also to this post:

And more important refer to Apache2 docs

I think your set up is like mine. I’ll post here the entire set up. I use AJP and JK with Apache to hand off CF requests to Tomcat.

The part you may be interested in especially is the file /etc/apache2/my-apache-cf-rewrite.conf which I use to pass off URL rewrites to CF. That allows me to have friendly URL’s.

This is a guide that you can follow and only have to do once. After that, you can upgrade any piece of the stack you want without breaking it. So, you can upgrade Apache and not worry about anything else unless Apache has made some groundbreaking, earth shattering change. You can upgrade Tomcat or OpenJDK or Lucee independent of anything else as well.


Ubuntu 20.04.02 LTS - everything is up to date

Apache 2.4.41

MySQL 8.0.23

Open SSL 1.1.1f

Tomcat 9.0.41

Open JDK 16


PHP 7.4.3

I don’t use any package manager or anything - though I probably should because all the cool kids do. I just like to know how to work each piece of the stack and also, I can change or upgrade OpenJDK versions in just a few seconds which is nice to be able to do to stay up to date with Java.

I have several Cold Fusion sites as well as several Word Press sites. I try to keep everything as “stock” as possible so that upgrades are timely and easy. I don’t like to break things.


Apache modules enabled:

 core_module (static)
 so_module (static)
 watchdog_module (static)
 http_module (static)
 log_config_module (static)
 logio_module (static)
 version_module (static)
 unixd_module (static)
 access_compat_module (shared)
 alias_module (shared)
 auth_basic_module (shared)
 authn_core_module (shared)
 authn_file_module (shared)
 authz_core_module (shared)
 authz_host_module (shared)
 authz_user_module (shared)
 autoindex_module (shared)
 deflate_module (shared)
 dir_module (shared)
 env_module (shared)
 expires_module (shared)
 filter_module (shared)
 headers_module (shared)
 jk_module (shared)
 mime_module (shared)
 mpm_event_module (shared)
 negotiation_module (shared)
 proxy_module (shared)
 proxy_ajp_module (shared)
 proxy_connect_module (shared)
 proxy_fcgi_module (shared)
 proxy_html_module (shared)
 proxy_http_module (shared)
 reqtimeout_module (shared)
 rewrite_module (shared)
 setenvif_module (shared)
 socache_shmcb_module (shared)
 ssl_module (shared)
 unique_id_module (shared)
 xml2enc_module (shared)

In /etc/apache2/apache2.conf - mostly stock, the things you might want to check are:

# Sets the default security model of the Apache2 HTTPD server. It does
# not allow access to the root filesystem outside of /usr/share and /var/www.
# The former is used by web applications packaged in Debian,
# the latter may be used for local directories served by the web server. If
# your system is serving content from a sub-directory in /srv you must allow
# access here, or in any related virtual host.
<Directory />
	Options +FollowSymLinks +Includes -Indexes
	AllowOverride None
	Require all denied

<Directory /usr/share>
	AllowOverride None
	Require all denied

# set root of web directory here
<Directory /path/to/your/websites/>
	Options +FollowSymLinks +Includes -Indexes
	AllowOverride None
	Require all granted

#<Directory /srv/>
#	Options Indexes FollowSymLinks
#	AllowOverride None
#	Require all granted

Once Apache is up and running, no problems, let’s create 3 custom includes for Apache 2 that I use in virtual host conf files:


# Start: Tomcat for Lucee configuration with proxy+ajp13:
# added [ProxyRequests Off] seems to have no effect, left it here because why not
ProxyRequests Off
ProxyPreserveHost On
# Skip over these directories
ProxyPass /images !
ProxyPass /lib !
# Skip over these fles types
ProxyPass ^/(.+\.js)$ !
ProxyPass ^/(.+\.html)$ !
ProxyPass ^/(.+\.htm)$ !
ProxyPass ^/(.+\.php)$ !
ProxyPass ^/(.+\.gif)$ !
ProxyPass ^/(.+\.gz)$ !
ProxyPass ^/(.+\.ico)$ !
ProxyPass ^/(.+\.jpg)$ !
ProxyPass ^/(.+\.jpeg)$ !
ProxyPass ^/(.+\.mov)$ !
ProxyPass ^/(.+\.png)$ !
ProxyPass ^/(.+\.tar)$ !
ProxyPass ^/(.+\.tif)$ !
ProxyPass ^/(.+\.tiff)$ !
ProxyPass ^/(.+\.pdf)$ !
ProxyPass ^/(.+\.txt)$ !
ProxyPass ^/(.+\.xml)$ !
ProxyPass ^/(.+\.zip)$ !
# Main directives
ProxyPassMatch ^/(.+\.cf[cm])(/.*)?$ ajp://$1$2
ProxyPassMatch ^/(.+\.cfchart)(/.*)?$ ajp://$1$2
ProxyPassMatch ^/(.+\.cfml)(/.*)?$ ajp://$1$2
# ProxyPassReverse does not matter in our case
ProxyPassReverse / ajp://
# Alternate, minimum settings (remove everything above this) if you need to do this
# ProxyPreserveHost On
# ProxyPassMatch ^/([cm])(/.)?$ ajp://$1$2
# ProxyPassMatch ^/((flashservices/gateway|messagebroker/|flex2gateway/|openamf/gateway/).) ajp://$1


RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule ^(.*)$ index.cfm?varString=$1 [L,QSA]

I also use a file to secure Lucee admin:


## secure the lucee admin behind SSL and basic auth

<Location /lucee-context>

<Location /lucee/admin>
	AuthType Basic
	AuthUserFile /etc/apache2/passwords/webauth
	Require user YZYZYZYZYZ

<Location /lucee-server>
	AuthType Basic
	AuthName "XXXXXXXX"
	AuthUserFile /etc/apache2/passwords/webauth
	Require user YZYZYZYZYZ

## secure the lucee admin behind SSL and basic auth

Now, assuming that Tomcat and Java / Lucee are correctly installed (I will come back to that), here is an example CONF file for a virtual host

virtual-host.conf example

SSLStrictSNIVHostCheck on
<VirtualHost *:443>

	DocumentRoot /path/to/your/web/site
	DirectoryIndex index.html index.cfm
	RewriteEngine On
	RewriteOptions Inherit
	SSLEngine on
	Include /etc/letsencrypt/options-ssl-apache.conf
	SSLCertificateFile /etc/letsencrypt/live/
	SSLCertificateKeyFile /etc/letsencrypt/live/

	Include /etc/apache2/my-apache-cf-ajp.conf
	Define luceeadmin "XXXXXX"
	Include /etc/apache2/my-apache-cf-secure-lucee.conf
	Define listofusers "XXXXXX AAAAAAA"
	Include /etc/apache2/my-apache-secure-directories.conf
	<Directory "/path/to/your/web/site">
		AllowOverride None
		Include /etc/apache2/my-apache-cf-rewrite.conf
	CustomLog /path/to/your/web/site/log/ vhost_combined
	ErrorLog /path/to/your/web/site/log/
	LogLevel error
	# CF Site

Now let’s look at Tomcat

I put Tomcat in /opt/tomcat and this is a complete “HOW-TO” for installing Tomcat and having it load at boot and always running - YMMV

  1. Make sure you have a valid openssl version, you probably do:

openssl version

should show 1.1.1b or greater

  1. Make /opt/tomcat and change permissions
chgrp -R tomcat /opt/tomcat
chown -R tomcat:tomcat /opt/tomcat

I then CURL the latest Tomcat to /tmp

cd /tmp

curl -O

unpack into /OPT

mkdir /opt/tomcat

(you are still in /tmp which is correct, we unpack from there and move it)

tar xzvf apache-tomcat-9*tar.gz -C /opt/tomcat --strip-components=1

Set up new tomcat group on the system

groupadd tomcat

then add tomcat user

useradd -s /bin/false -g tomcat -d /opt/tomcat tomcat

Give the tomcat group ownership over the entire installation directory:

chgrp -R tomcat /opt/tomcat

chown -R tomcat:tomcat /opt/tomcat

cd /opt/tomcat

Next, give the tomcat group read access to the conf directory and all of its contents, and execute access to the directory itself

chmod -R g+r conf
chmod g+x conf

Make the various shell scripts executable:

cd /opt/tomcat/bin
chmod +x *.sh

Next, set up a start up script in /etc/systemd/system/tomcat.service

Note: this script depends on the information found in the section on installing java because the environment varibale shown below has to be the same as in /etc/environment and /etc/systemd/system/tomcat.service.

nano /etc/systemd/system/tomcat.service

Description=Apache Tomcat Web Application Container


Environment='CATALINA_OPTS=-Xms2G -Xmx2G -server -XX:+ZGC'




Save that and exit

Reload system daemon

	systemctl enable tomcat
	systemctl daemon-reload

Now, make sure you have /opt/tomcat/ and the entire contents will be:

export CATALINA_OPTS="-Xms2G -Xmx2G -Xmn2048m -Xss1024k -XX:+UseZGC -XX:ConcGCThreads=4 -Djava.awt.headless=true -Duser.timezone=America/New_York";

Start tomcat

systemctl start tomcat

Test to see if it is running

systemctl status tomcat

If not already, update firewall (we will close it later)

ufw allow 8080

For Tomcat web manager which we are only going to use to test the setup

In /opt/tomcat/conf/tomcat-users.xml

<role rolename="manager-gui"/>
<role rolename="admin-gui"/>
<user username="ABC123" password="DEF456" roles="manager-gui,admin-gui"/>

**In BOTH **

Inside those files, comment out the IP address restriction to allow connections from anywhere. Alternatively, if you would like to allow access only to connections coming from your own IP address, you can add your public IP address to the list:

<Context antiResourceLocking="false" privileged="true" >
	 <!-- <Valve className="org.apache.catalina.valves.RemoteAddrValve"
		 allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1" /> -->

Now. Open /opt/tomcat/conf/server.xml

<Server port="8005" shutdown="SHUTDOWN">
<Server port="8005" shutdown="-1">

Save and close.

*Now. Open /opt/tomcat/conf/web.xml

Around line 600~ make the following changes:


Save and close.

Restart Tomcat - this is the correct way now to stop and start

systemctl restart tomcat

If Tomcat is working, you can point your browser to the domain where you set it up and append the :8080 port and you should see the welcome screen.

At this point, a hard reboot is nice to do just to make sure everything is solid. YMMV

Once rebooted, let’s close port 8080

ufw deny 8080

INSTALLING OPEN JDK (versions 10, 11, 12, 13, 14, 15, 16)

create teh jdk directory

mkdir /opt/java

cd /opt/java

curl -O

Uncompress and unpack and change to your directory name

tar -zxvf openjdk-16_linux-x64_bin.tar.gz -C /opt

Now, rename the unpacked directory to “java”

mv -v /opt/dk-16 /opt/java

*NOTE: If later on, you upgrade to v17, just stop Tomcat, change the directory name of /opt/java to something like /opt/java-16 then upack the new version and rename it to /opt/java

Now, we’ll set the Java home variable for the system.


restart your shell and run


nano /etc/environment



Finally, for good measure

systemctl daemon-reload


I like to clear out Tomcat logs when I do this:

systemctl stop tomcat	

echo "" > /opt/tomcat/logs/catalina.*.log

echo "" > /opt/tomcat/logs/*.out

rm -R /opt/tomcat/logs/*

Then download Lucee (make sure you have the right URL)

cd /tmp

curl -O /tmp/lucee-

rm -R /opt/tomcat/webapps/ROOT
mv -v /opt/tomcat/webapps/ROOT /opt/tomcat/webapps/mytomcat

cp -p /tmp/lucee- /opt/tomcat/webapps/ROOT.war

Restart Tomcat and Lucee should now be running

systemctl start tomcat

Open 8443 (temporarily)

UFW allow 8443

Test: Go to Splash page

I have done this process now on Ubuntu 10, 12, 14, 18 and 20, on both my own server hardware as well as on droplets at Digital Ocean, as well as on several versions of Mac OS.


Hi @andreas,

Thankyou. I am able to test it today.
The solution is working for url rewrite. But can we handle this through Application.cfc onMissingTemplate function means if cfm doesn’t exist then it should run onMissingTemplate function?


But this is not what you’ve been asking for at first. You wanted to redirect to a custom 404.cfm as you’ve done in IIS and you described here.

Still what you want can be done, but this approach differs a lot from what you’ve been asking for, so here comes a quite short answer…

You need to adapt the urlRewirte and rewrite everything to one index.cfm, catch the URI data in the RegEx and append it as a query string or path to that index.cfm. Then in the Application.cfc you can handle everything else.

Yes. That is one case. I just found this code too in onMissingTemplate function.

    <cffunction name="onMissingTemplate">
            <cfargument name="targetPage" type="string" required=true/>
            <!--- Use a try block to catch errors. --->
                    <cfinclude template="site_UrlRewrite.cfm">
                    <cfreturn false />
                    <!--- If no match, return false to pass back to default handler. --->
                    <cfdump var="#cfcatch#">
                        <!--- Do some error logging here --->
                        <cfreturn false />

The URLReWrite file is showing different content based on the cgi.path_info .

So based on this. I have made little change in RewriteRule.

 RewriteCond %{REQUEST_FILENAME} !-f
 RewriteCond %{REQUEST_FILENAME} !-d
 RewriteRule "^(.*)$" "$1$2" [P]

This rewrite is working as expected. But i am afraid if i am not missing any case.