Skip to main content

ULTIMATE GUIDE: Setting Up Your Server for Reliable Email Delivery. PART 2: How to prevent outgoing emails from going to spam

· 8 min read
Customer Care Engineer

dns-records-configuration-spf-dkim-dmarc-ptr-mx-prevent-email-spam-guide

info

DNS records are a set of technical parameters of a domain that determine where to route different types of traffic: web, mail, FTP, etc.

They link the domain name to IP addresses and other servers so that browsers and mail systems know where to connect when working with your website or email.

In Part 1 of this guide, we configured the firewall and opened the necessary ports for mail delivery. Now that we have made sure that messages are being sent from your server, we need to check that the DNS records are configured correctly. Gmail, Outlook, and other major mail services strictly verify MX, SPF, DKIM, DMARC, and PTR before delivering messages to the primary inbox. Correct records sharply increase the chance that messages will land in the Inbox folder instead of Spam (or be completely rejected).

In the following steps, we will describe how to check the current DNS records of each type and provide recommendations on how to configure them correctly.

warning

In this part of the article, for all checks we use the domain example.com, IPv4 address 1.2.3.4 and IPv6-Address 2001:db8:1:11::1 as examples. Please replace them with the real name of your website and your actual IP addresses.

Pre-requisites

Step 1. Checking the DNS provider

In the context of this article, we will need to edit DNS records multiple times. This must be done in the control panel of the current DNS provider for your domain. To do this correctly, you need to know exactly who is providing these services right now. Otherwise, the changes you make will have no effect.

Most often the role of DNS provider is performed by:

  • your hosting provider,

  • your domain registrar, or

  • your DDoS protection provider.

But this is not always the case.

You can find out exactly who is the DNS provider for your domain at the moment using the whois command. For example, let us check the domain kodu.cloud:

whois kodu.cloud | grep -i "Name Server" | awk '{print $3}'

The output will list the NS servers:

art.ns.cloudflare.com
isla.ns.cloudflare.com

That means that in this case DNS records must be edited in the Cloudflare control panel.

In many cases, you can infer who the DNS provider is from the domain name. However, this is not always so. For example, for our free DNS service, which is available to all our customers, the names look like this:

birman.nameserver.red.
rex.nameserver.red.
korat.nameserver.blue.
toybob.nameserver.blue.

 In this case, please contact your hosting provider’s technical support for further clarification.

After you have definitively identified your DNS provider, log in to their control panel and go to the section for editing DNS records for your domain. Most of the changes described below will be made there. Since all interfaces differ, it is impossible to provide a universal step-by-step guide.

If you encounter difficulties at this stage, consult technical support.

warning

After changing DNS records, cache propagation can take from 20 minutes to 24 hours. Taking into account the time required for major mail services to update your reputation, for stable mail delivery it is recommended to wait about 48 hours, although more often the situation normalizes after 2-3 hours.

Step 2. Checking for IPv6

For correct configuration, we need to know exactly whether the server has an IPv6 address. To find out, run the command:

ip a | grep inet6

If the output is approximately as follows:

inet6 ::1/128 scope host noprefixroute
inet6 2001:db8:1:11::1/120 scope global
inet6 fe80::1/64 scope link

then your server has an IPv6 address. It will be shown on the line ending with scope global up to the / sign.

Write down your server’s IPv6 address so that you can use it later.

If the output is empty, or contains only a single line of the form:

inet6 fe80::1c2:3dff:fe4a:5b6c/64 scope link

then your server does not have IPv6. In all subsequent steps, please do not use IPv6.

DNS record configuration

Step 3. Checking the MX record

The MX record specifies where incoming mail for the domain should be delivered. You can check it with the command:

dig MX example.com +short

Example of correct output:

10 mail.example.com.

If the output shows an incorrect value, edit the DNS record in your DNS provider’s control panel:

  • Type: MX
  • Name: @
  • Address: mail.example.com
  • Priority: 10

If the record is missing, you will get an empty output:

~ dig MX badexample.com +short
~

In this case, create a DNS record following the above example.

Also note that there can be several MX  records. In this case, mail will first be sent to the server with the smaller numeric priority value (for example, 10 has a higher priority than 20).

If the primary server is unavailable, delivery will be performed to the next server in order of priority.

Step 4. Checking A and AAAA records

Next, make sure that mail.example.com and example.com actually point to the server from which messages are being sent:

dig A example.com +short
dig A mail.example.com +short
dig AAAA example.com +short
dig AAAA mail.example.com +short

The IP addresses in the output of these commands must match the actual IPv4 and IPv6 addresses of your server. If they differ, or if such records are missing, edit or create them in your DNS provider’s control panel:

  1. example.com (А):
  • Type: A
  • Name: @
  • Address: 1.2.3.4
  1. example.com (АAAA):
  • Type: AAAA
  • Name: @
  • Address: 2001:db8:1:11::1
  1. mail.example.com (A):
  • Type: A
  • Name: mail
  • Address: 1.2.3.4
  1. mail.example.com (AAAA):
  • Type: AAAA
  • Name: mail
  • Address: 2001:db8:1:11::1

Step 5. Checking the SPF record

SPF defines which servers are allowed to send mail on behalf of the domain.

Checking the record:

dig TXT example.com +short

Example of a correct SPF record:

example.com. TXT "v=spf1 ip4:1.2.3.4 ip6:2001:db8:1:11::1 a mx ~all"

It is extremely important that the domain has only one SPF record. Multiple records can cause a validation error.

If the IP address differs from your current server address, or if such a record is missing, edit or create it in your DNS provider’s control panel:

If IPv6 is present:

  • Type: TXT
  • Name: @
  • Value: v=spf1 ip4:1.2.3.4 ip6:2001:db8:1:11::1 a mx ~all

If there is no IPv6:

  • Type: TXT
  • Name: @
  • Value: v=spf1 ip4:1.2.3.4 a mx ~all

The SPF record has many additional options, but this set is sufficient for correct message sending.

Step 6. Checking the DKIM record

DKIM is a digital signature of a message that confirms that it was sent from the specified domain and has not been forged. A TXT DNS record with a public key is used to verify the signature: first the signature is created on the server, and then the key is published in DNS.

There are various ways to obtain a DKIM signature for your mail domain, but within the scope of this article we will consider the simplest option - using FASTPANEL. In FASTPANEL, a DKIM signature is created for mail domains by default.

FASTPANEL is extremely convenient for working with a mail server: you do not need to configure or install anything manually. Out of the box, you get a configured mail server for full-fledged work with incoming and outgoing mail, as well as a convenient webmail client.

Therefore, all you have to do is copy the public key value and add it to the corresponding DNS record. To do this, in the panel, go to “Management” → “Mail” and click on the “DKIM” button at the end of the row with the name of your mail domain:

dns-records-configuration-spf-dkim-dmarc-ptr-mx-prevent-email-spam-guide

Then copy the public key:

dns-records-configuration-spf-dkim-dmarc-ptr-mx-prevent-email-spam-guide

On the side of your DNS provider, create the following DNS record:

  • Type: TXT
  • Name: dkim._domainkey
  • Value: the key that you copied earlier

If such a record already exists, it is better to still replace the key value to be sure it is up to date.

There can be several DKIM records, but in that case they must have different names. For example, having records with the names dkim._domainkey and mail._domainkey at the same time will not cause any problems.

After configuration and DNS cache update, you can check for the presence of the DKIM record using the command:

dig TXT dkim._domainkey.example.com +short

Step 7. Checking the DMARC record

DMARC sets the rules for handling messages that fail SPF or DKIM.

Checking the record:

dig TXT _dmarc.example.com +short

Example output:

"v=DMARC1;p=reject;sp=reject;adkim=s;aspf=s"

The DMARC record has many additional options, but for common scenarios it is sufficient to add a record of the following form:

  • Type: TXT
  • Name: _dmarc
  • Value: v=DMARC1; p=reject

Step 8. Checking the sendmail_path variable (optional)

This step will be very useful if you send messages directly from your website (for example, using contact forms or when placing orders). To ensure that all necessary DNS records are taken into account for such messages, set the following value for the sendmail_path variable in your site’s PHP settings:

/usr/sbin/sendmail -t -i -f "[email protected]"

For example, in FASTPANEL you can do this in the site card in the “PHP Settings” section.

Hostname and PTR configuration

PTR (or rDNS) is a reverse DNS record that links an IP address to a domain name.

The server Hostname is simply the name by which the server can be identified on the network.

If the PTR does not match the server hostname, messages will almost certainly end up in spam.

Best practice is to set mail.example.com as both values (remember that this value must always be replaced with the real name of your domain).

Step 9. Hostname

To find out your server’s current hostname, run the command:

hostname

To change it, run:

sudo hostnamectl set-hostname mail.example.com

Then run the hostname command again to make sure that everything is correct.

Step 10. PTR

The situation with the PTR record is somewhat different: it can be changed only by the owner of the IP address, that is, your hosting provider. You can learn more about the reasons for this from this article.

Some hosting providers allow you to edit the PTR record in your personal account. You can try to do this there yourself. If that does not work, contact technical support and ask them to set the following value as the PTR record:

mail.example.com

Remember that this value must always be replaced with the real name of your domain.

Changes are usually applied immediately; there is no need to wait for DNS cache updates in this case. You can check the current PTR record using the command:

dig -x 1.2.3.4 +short

Final check

If you have reached this section, congratulations - you are on the home stretch. Now all that remains is to verify that everything is configured correctly and that messages from your server are almost guaranteed to end up in the Inbox for any recipient. Before doing this, be sure to wait for DNS cache updates. It is best to wait at least 1-2 hours after the last changes to DNS records.

There are many third-party services for such checks. You can use any of them. In this article, we will use the mail-tester.com service.

To perform the check, go to this website and copy the name of the test mailbox:

dns-records-configuration-spf-dkim-dmarc-ptr-mx-prevent-email-spam-guide

Send a message to this address from any mailbox on your domain and click the “Then check your score” button. Do not forget to specify a subject and write something in the body of the message.

After that, the service will show the results of checking DKIM, SPF, DMARC, the presence of a PTR record and an overall “reputation” score for the message, as well as whether your server is listed in SPAM blacklists.

If you have carefully and consistently followed all the steps in this article, the result will be 10/10. If it is lower, please study mail-tester’s recommendations. They will explain in detail what the problem is and how to fix it.

Additionally, you can check whether your server is listed in SPAM blacklists using another excellent service:

 https://mxtoolbox.com/blacklists.aspx

If any of the checks show that your server’s IP address has ended up in one of the lists, contact your hosting provider so that they can help resolve the issue.

That concludes the configuration. If you do not want to spend time on your own research or have run into any problems, you can always contact kodu.cloud technical support. We will perform all the steps described here as quickly as possible for our clients at any time, free of charge.

ULTIMATE GUIDE: Setting up your server for reliable email delivery. PART I: Firewall

· 10 min read
Customer Care Engineer

email-server-linux-firewall-configuration-smtp-imap-setup-guide

info

Firewall  is software (or a hardware-software appliance) that controls which connections to the server are allowed and which are blocked. In the vast majority of modern Linux server distributions, some form of firewall is available out of the box.

Reliable email delivery to the recipient depends not only on the mail server itself, but also on correct DNS records and firewall configuration. If something is wrong with those, your messages are very likely to land in the Spam folder - or not be delivered at all.

This article outlines the key steps that will allow you to achieve nearly 100% reliable delivery of messages sent from your server. In Part 1, we will walk through the possible firewall-related issues in detail; in Part 2, you will find instructions for configuring DNS records.

The information in this article applies only to mail servers running on Linux distributions. Debian 12 and Rocky Linux 8.10 with the FASTPANEL control panel are used as examples.

Pre-requisites

Step 1. Install diagnostic tools

To check records and ports, you will need:

  • dig - for analyzing DNS records

  • lsof - for checking the mail server state

  • netcat - for checking port availability

  • whois - for checking the current DNS provider

Installation on Debian/Ubuntu:

sudo apt update && sudo apt install -y bind9-dnsutils netcat-openbsd lsof whois

CentOS/AlmaLinux/Rocky Linux:

sudo yum install -y bind-utils nmap-ncat lsof whois

Mail port availability

info

A port is a numerical identifier used to address different services on a server. Each service or application listens on its own port to exchange data over the network (for example, HTTP uses port 80, and SMTP uses port 25).

To perform all the following steps, connect to your server over SSH using the root user credentials or use sudo when running commands, as shown in the examples. You can learn how to connect to a server via SSH in our SSH article.

Step 2. Check mail server status

Before checking whether the ports are reachable over the network, first make sure that the mail servers are running and working correctly. Outgoing mail is usually handled by Exim or Postfix, and incoming mail by Dovecot.

To check that they are running, use the commands:

sudo lsof -i:25
sudo lsof -i:143

If the services are running, you will get an output similar to the following:

Port 25:

COMMAND PID USER   FD   TYPE    DEVICE SIZE/OFF NODE NAME
exim    839 exim    4u  IPv6 778199358      0t0  TCP *:smtp (LISTEN)
exim    839 exim    5u  IPv4 778199359      0t0  TCP *:smtp (LISTEN)

If your server uses a different SMTP server, for example Postfix, you will see its exact name instead of exim in the first column. If needed, use that name in subsequent commands.

Port 143:

COMMAND PID USER   FD   TYPE    DEVICE SIZE/OFF NODE NAME
dovecot 859 root   39u  IPv4 778204692      0t0  TCP *:imap (LISTEN)
dovecot 859 root   40u  IPv6 778204693      0t0  TCP *:imap (LISTEN)

This means everything is fine and you can proceed to the next step.

If the mail services are not running, you will get an empty output:

~ sudo lsof -i:25
~
~ sudo lsof -i:143
~

In this case, something is wrong and the mail services are not available. You can try starting them manually and then check their status:

Debian/Ubuntu:

systemctl restart exim4 dovecot
systemctl status exim4
systemctl status dovecot

CentOS/AlmaLinux/Rocky Linux:

systemctl restart exim dovecot
systemctl status exim
systemctl status dovecot

In the running state, the service status will look similar to:

Exim:

● exim.service - Exim Mail Transport Agent
   Loaded: loaded (/usr/lib/systemd/system/exim.service; enabled; vendor preset: disabled)
   Active: active (running) since Sun 2025-11-02 16:38:57 UTC; 57min ago
 Main PID: 839 (exim)
    Tasks: 1
   Memory: 11.0M
   CGroup: /system.slice/exim.service
           └─839 /usr/sbin/exim -bd -q1h

Warning: Journal has been rotated since unit was started. Log output is incomplete or unavailable.

Dovecot:

● dovecot.service - Dovecot IMAP/POP3 email server
   Loaded: loaded (/usr/lib/systemd/system/dovecot.service; enabled; vendor preset: disabled)
   Active: active (running) since Sun 2025-11-02 16:38:58 UTC; 58min ago
     Docs: man:dovecot(1)
           https://doc.dovecot.org/
 Main PID: 859 (dovecot)
    Tasks: 5
   Memory: 9.5M
   CGroup: /system.slice/dovecot.service
           ├─ 859 /usr/sbin/dovecot -F
           ├─ 880 dovecot/anvil
           ├─ 881 dovecot/log
           ├─ 882 dovecot/config
           └─1729 dovecot/stats

In this case, everything is fine and you can move on to the next step.

If something is wrong with the services, you will see output similar to this:

Exim:

● exim.service - Exim Mail Transport Agent
   Loaded: loaded (/usr/lib/systemd/system/exim.service; enabled; vendor preset: disabled)
   Active: inactive (dead) since Sun 2025-11-02 17:38:44 UTC; 3s ago
  Process: 839 ExecStart=/usr/sbin/exim -bd -q${QUEUE} (code=exited, status=0/SUCCESS)
 Main PID: 839 (code=exited, status=0/SUCCESS)

Dovecot:

● dovecot.service - Dovecot IMAP/POP3 email server
   Loaded: loaded (/usr/lib/systemd/system/dovecot.service; enabled; vendor preset: disabled)
   Active: inactive (dead) since Sun 2025-11-02 17:39:32 UTC; 3s ago
     Docs: man:dovecot(1)
           https://doc.dovecot.org/
  Process: 2278 ExecStop=/usr/bin/doveadm stop (code=exited, status=0/SUCCESS)
  Process: 859 ExecStart=/usr/sbin/dovecot -F (code=exited, status=0/SUCCESS)
 Main PID: 859 (code=exited, status=0/SUCCESS)

There are many possible reasons why the services might be unavailable (errors in configuration files, accidentally deleted log files, lack of free disk space on the server, and much more). These aspects are outside the scope of this article.

If you run into this issue and want to investigate it yourself, we recommend our article on working with the system journal. Or contact your hosting provider’s support team for assistance. At kodu.cloud we work 24/7 and respond to requests within a few minutes.

Step 3. Checking the availability of mail ports from the global network

For email to work correctly, the following TCP ports must be accessible over the network:

  • 25, 465, 587 - for sending mail (SMTP)

  • 143, 993 -  for receiving mail (IMAP)

You can check their availability using netcat:

nc -vz 1.2.3.4 25

Replace 1.2.3.4 with the actual IP address of your server.

If the port is open, you will get the following response:

Debian/Ubuntu:

Connection to 1.2.3.4 25 port [tcp/smtp] succeeded!

CentOS/AlmaLinux/Rocky Linux:

Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Connected to 1.2.3.4:25.
Ncat: 0 bytes sent, 0 bytes received in 0.01 seconds.

If the port is closed, the response will be as follows:

Debian/Ubuntu:

nc: connect to 1.2.3.4 port 25 (tcp) failed: Connection refused

CentOS/AlmaLinux/Rocky Linux:

Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Connection refused.

Run this command for all ports listed at the beginning of this section. If all of them are available, proceed to the second part of this article. If not, go to Step 4 to fix the issue.

Step 4. Opening access (optional)

Most often, servers use iptables or UFW as a firewall to protect against unwanted connections. Run the command:

sudo ufw status

If you receive the following response:

-bash: ufw: command not found

it means that iptables is used. If, however, the output starts with:

Status: active

It means that UFW is used.

Go to the relevant subsection of this step according to the firewall in use.

warning

There is other software for managing the system firewall in Linux (nftables, firewalld, and others). These will not be covered in this article in order not to increase its size.

Iptables

warning

In this section, all examples apply only to IPv4. If your server has an IPv6 address and you also need the mail ports to be available over IPv6, additionally repeat all commands using ip6tables instead of iptables.

To display all current rules, run:

sudo iptables -L -v -n --line-numbers

The output may differ depending on the set of rules added on the server. By default, there are no rules, but depending on the hosting configuration or operating system, some configuration may have already been performed. Let us look at two main cases that are often encountered when configuring a mail server.

  1. “Everything that is not forbidden is allowed” policy (policy ACCEPT)

In this case, the firewall, by default, allows all traffic until a rule explicitly blocking it is created. This policy is usually used by default.

Below is an example of a ruleset where SSH and mail ports are blocked:

Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1       51  5637 DROP       6    --  *      *       0.0.0.0/0            0.0.0.0/0            tcp multiport dports 22
2        0     0 DROP       6    --  *      *       0.0.0.0/0            0.0.0.0/0            tcp multiport dports 25,143,993,587,465

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 DROP       6    --  *      *       0.0.0.0/0            0.0.0.0/0            tcp multiport dports 25,143,993,587,465

In this example:

  • Policy ACCEPT defines the default behavior: all connections are allowed unless stated otherwise.

  • Chain INPUT (incoming connections): new connections to port 22 (SSH) and to all standard mail ports (SMTP: 25, 465, 587; IMAP: 143; IMAPS: 993) are blocked.

  • Chain OUTPUT (outgoing connections): outgoing packets to the same ports are blocked.

The rule number is shown at the beginning of the line (num). If you need to delete, for example, the rule that blocks the mail ports in the INPUT chain (rule number 2), the command will be:

sudo iptables -D INPUT 2

Here:

  • -D - is the command to delete a rule.

  • INPUT is the chain from which the rule is being deleted (in this case, incoming traffic).

  • 2 is the line number where the rule you want to delete is located.

Similarly, to remove the block on outgoing ports from this example, you need to run:

sudo iptables -D OUTPUT 1
warning

The line numbers in the output of iptables -L -v -n --line-numbers may change if you add or delete other rules. Therefore, if you add new rules, the line numbers may shift. To avoid mistakes, always check the current line numbers before deleting a rule.

After this, to make sure everything is correct, run the command again:

iptables -L -v -n --line-numbers

The output should not contain rules that block the operation of the mail ports:

Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1       51  5637 DROP       6    --  *      *       0.0.0.0/0            0.0.0.0/0            tcp multiport dports 22

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination

If the rules are still present, check the correctness of the commands you entered and the line numbers.

After changing the firewall rules, be sure to save the changes; otherwise they will be lost after a reboot. To save the current iptables rules, run:

Debian/Ubuntu:

iptables-save > /etc/iptables/rules.v4

CentOS/AlmaLinux/Rocky Linux:

iptables-save > /etc/sysconfig/iptables

After that, test the availability of the mail ports again, as described in Step 3. If they are still unavailable, proceed to Step 5. If everything is in order, proceed to the second part of this article.

2. “Block everything that is not explicitly allowed” policy (policy DROP)

This type of configuration is much stricter. All ports and protocols are blocked by default, and only explicit allow rules for specific ports let traffic through. This is a more secure and recommended approach for servers, especially if you want to minimize risks.

An example of rules for such a configuration:

Chain INPUT (policy DROP 306 packets, 17962 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1       92 12474 ACCEPT     0    --  *      *       0.0.0.0/0            0.0.0.0/0            state RELATED,ESTABLISHED
2      466 32947 ACCEPT     6    --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy DROP 197 packets, 29192 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1       44 10332 ACCEPT     0    --  *      *       0.0.0.0/0            0.0.0.0/0            state RELATED,ESTABLISHED

In this example:

  • Chain INPUT (incoming connections): the policy is DROP, that is, all packets are blocked by default, except for those for which there are allow rules.

Rule 1: ACCEPT state RELATED,ESTABLISHED - allows incoming packets that relate to existing connections or are associated with them. This is important for the server to correctly receive responses to outgoing connections (for example, SSH, mail, DNS queries). Rule 2: ACCEPT tcp dpt:22 - allows new incoming connections on port 22 (SSH).

  • Chain OUTPUT (outgoing connections): the policy is DROP, that is, all outgoing packets are blocked by default.

Rule 1: ACCEPT state RELATED,ESTABLISHED - allows outgoing packets that relate to existing connections. This enables the server to respond to client requests (for example, SSH, mail connections) and maintain a stable connection.

So we see that there are no allow rules for the mail ports. To add them, run the following commands:

sudo iptables -A INPUT -p tcp -m multiport --dports 25,465,587,143,993 -m state --state NEW,ESTABLISHED -j ACCEPT
sudo iptables -A OUTPUT -p tcp -m multiport --dports 25,465,587,143,993 -m state --state NEW,ESTABLISHED -j ACCEPT

After that, save the rules:

Debian/Ubuntu:

iptables-save > /etc/iptables/rules.v4

CentOS/AlmaLinux/Rocky Linux:

iptables-save > /etc/sysconfig/iptables

Then test the availability of the mail ports again, as described in Step 3. If they are still unavailable, proceed to Step 5. If everything is in order, proceed to the second part of this article.

UFW (Uncomplicated Firewall)

On some servers, UFW is used instead of iptables - a simplified interface for configuring the firewall. It is suitable for basic mail server configuration and is often found on Ubuntu and Debian.

To see whether UFW is enabled and which rules are active, run:

sudo ufw status verbose

Example output:

Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing)
New profiles: skip

Here:

  • Default: deny (incoming) - by default, all incoming connections are blocked.

  • Default: allow (outgoing) - by default, all outgoing connections are allowed.

For the mail server to work correctly, you need to open the standard ports. Run the following commands in sequence:

sudo ufw allow 25/tcp
sudo ufw allow 465/tcp
sudo ufw allow 587/tcp
sudo ufw allow 143/tcp
sudo ufw allow 993/tcp

If you also have outgoing restrictions, you need to allow SMTP ports for outgoing mail:

sudo ufw allow out 25/tcp
sudo ufw allow out 465/tcp
sudo ufw allow out 587/tcp

After adding the rules, you can check them again:

sudo ufw status numbered

You will see a list of rules with numbers. If you need to delete a rule, use the number from the output:

sudo ufw delete 3

In UFW, rules are saved automatically and will remain in effect after a reboot.

Then test the availability of the mail ports again, as described in Step 3. If they are still unavailable, proceed to Step 5. If everything is in order, proceed to the second part of this article.

Step 5. The firewall is configured correctly, but the mail ports are still unavailable (optional)

Such behavior indicates that the mail ports are blocked not at the level of your server, but on your hosting provider’s equipment. In this case, create a support ticket and ask them to unblock these ports for your server. If this is not possible, the only solution will be to change the hosting provider.

At kodu.cloud we do not restrict the operation of mail ports in any way. Our rules prohibit bulk mailings, but for bona fide customers email is available in full. You do not even need to configure a mail server, because all our clients are provided free access to the FASTPANEL control panel with an extended license. Order a server with the control panel and start using mail on your own domain in just a few minutes.

The following steps for proper DNS record configuration will be covered in the second part of this article.

WordPress error establishing a database connection: what it means and how to fix it

· 4 min read
Customer Care Engineer

wordpress-error-establishing-database-connection

If you’ve ever seen the “Error establishing a database connection” message on your WordPress site, you know how unpleasant it is. The site stops working, visitors see nothing but a white screen with that phrase, and anxiety gradually rises. In reality, this is one of the most common WordPress issues, and it can be fixed without panic or hiring outside specialists.

What is the “error establishing a database connection”?

WordPress runs on a PHP + MySQL/MariaDB stack. All your posts, pages, comments, and settings are stored in a database. When the CMS can’t connect to the database, the site simply doesn’t know where to get its data from. As a result, this error appears.

There are several possible causes:

  • Incorrect connection details (username, password, database name) in the wp-config.php in the site’s root directory.

  • Problems with the database server, such as MySQL/MariaDB overload or a crash.

  • A corrupted database, which sometimes happens after a failed WordPress update or an abrupt server shutdown.

  • Network restrictions or hosting issues that block the connection (if the DB connection is made over the network rather than locally).

How to check your connection details

The first thing to do is make sure WordPress has the correct database credentials:

  1. Open the wp-config.php file in the site root.

  2. Find the lines:

define('DB_NAME', 'database_name');

define('DB_USER', 'username');

define('DB_PASSWORD', 'password');

define('DB_HOST', 'localhost');
  1. Verify that all values match the settings specified when the database was created. Sometimes the issue comes down to a simple typo.

For example, in the FASTPANEL control panel you can find the connection parameters under “Management” → “Databases”. 

If you don’t remember the correct DB credentials, you can change the password yourself by following the instructions in this article. Or contact technical support for assistance.

Checking the database server

If the credentials are correct but the error persists, check the MySQL server itself:

  1. Try connecting to the database directly via phpMyAdmin or the MySQL CLI.

To connect using the CLI, log in to the server via SSH and run:

mysql -u user -p

Replace user with the username from wp-config.php, then enter the password. If the connection succeeds, you’ll see output similar to:

Welcome to the MySQL monitor.  Commands end with ; or \g.

Your MySQL connection id is 10498

Server version: 8.0.41 MySQL Community Server - GPL



Copyright (c) 2000, 2025, Oracle and/or its affiliates.



Oracle is a registered trademark of Oracle Corporation and/or its

affiliates. Other names may be trademarks of their respective

owners.



Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.


mysql>

This means the database server is operating correctly, and the database connection problem lies elsewhere. Carefully review the MySQL logs as well as the other tips in this article.

  1. If the connection can’t be established, the server may be overloaded or temporarily unavailable. In this case, you can contact your hosting support or restart MySQL/MariaDB.

To check whether the MySQL service is running, run:

systemctl status mysql

If the service is running, you’ll see a message like:

Active: active (running) since Wed 2025-08-20 20:40:25 UTC; 14h ago

Otherwise you’ll see:

 Active: inactive (dead) since Thu 2025-08-21 11:18:47 UTC; 865ms ago

There are many possible reasons for this MySQL service status - from lack of resources to configuration errors. To investigate, you can check the logs in the system journal or in /var/log/mysql/error.log.

Next, make sure the server’s hardware resource usage is within normal limits. To do this, use the htop utility. It may not be installed on your OS by default. To install it, run:

For Debian/Ubuntu:

sudo apt update && sudo apt install htop

For CentOS/AlmaLinux/Rocky Linux:

sudo yum install htop

Then run:

htop

An interactive window will open. Focus on the three main interface areas marked in the screenshot:

wordpress-error-establishing-database-connection

  1. Load Average

  2. Percentage of CPU usage and the amount of RAM in GB in use

  3. Top processes consuming the most resources

We won’t dive into these metrics in detail here. The key point is that high values for any of them indicate the server can’t handle the current load - which is likely the reason your site’s database is unavailable.

Often, the culprits behind high load are search crawlers that send too many requests to your sites. To try resolving this on your own, use this article.

If the load is normal, try starting the MySQL service again:

sudo systemctl restart mysql

Even if the server starts without errors after this, be sure to review the logs yourself or with the help of technical support. Otherwise, the situation may recur at the most unexpected moment with unpredictable consequences. 

Repairing a corrupted database

Sometimes the issue is a corrupted database. In that case, you’ll see log entries like:

[ERROR] mysqld: Table 'wp_options' is marked as crashed and should be repaired

[Warning] Checking table:   './wordpress/wp_posts'

[ERROR] Got error 127 when reading table './wordpress/wp_comments'

[ERROR] mysqld: Index for table 'wp_users' is corrupt; try to repair it

 WordPress has a built-in mechanism for repairing corrupted databases. To run it:

  1. Add the following line to wp-config.php:
define('WP_ALLOW_REPAIR', true);
  1. Navigate to https://your-site.com/wp-admin/maint/repair.php.

  2. Choose “Repair Database” or “Repair and Optimize Database.”

  3. When finished, be sure to remove this line from wp-config.php.

warning

It’s important to understand: the repair page only works if the MySQL server is running. If the database server is completely stopped, the site and the repair page will be equally unavailable.

What to do if nothing helps

If the site still doesn’t work, more serious issues are possible:

  • Your hosting provider has limited MySQL resources (relevant on shared hosting)

  • The database is too large and needs optimization

  • Due to natural growth of the DB and site traffic, the server no longer has sufficient resources for stable operation

  • Hardware failure on the server

  • MySQL configuration failure

  • A DDoS attack

In these cases, it’s best to contact hosting support. Professionals will check the server and, if necessary, restore data from a backup.

Conclusion

The WordPress database connection error is alarming, but most often it’s resolved by verifying the connection details or optimizing server load. The main thing is to stay calm and check each step methodically.

And in case of more serious problems, you can always contact our specialists for free technical support or restore your server yourself from backups, which are created automatically every day for all our VPS.

What is S3 storage and why your website needs it

· 2 min read
Customer Care Engineer

what-is-s3-storage

If you’re familiar with the term S3, you probably associate it with large, expensive cloud solutions like AWS, aimed at big projects with solid budgets. However, that’s not always the case. This protocol is more accessible than it seems and can be extremely useful for small projects and even personal use. In this article, we’ll demonstrate that clearly.

What is S3?

S3 (Simple Storage Service) is a protocol and service for object storage developed by Amazon Web Services (AWS). In essence, it’s a massive virtual warehouse for any files - from photos and videos to website and database backups. Unlike a regular disk on a server, S3 stores data in a distributed manner, making it more resilient and accessible from anywhere in the world.

The core idea of S3 is simplicity and universality. Every file you upload receives a unique address. This means you can easily access the file over the internet via HTTPS, share links, or connect it to a web application.

Why an S3 storage matters for a modern website

As your site grows, your server’s disk space can become insufficient. Videos, archives, and backups can quickly fill it up. S3 solves several problems at once:

  1. Scalability. You can increase storage capacity without buying new servers.

  2. Availability. Files remain accessible even if the primary server is temporarily down.

  3. Security. Data is distributed across multiple data centers or servers, reducing the risk of loss.

  4. Compatibility. S3 works with numerous applications and platforms - WordPress included - thanks to a unified data-exchange standard.

  5. Speed. Even with self-hosted solutions, performance is significantly higher than with traditional storage because they’re designed for large data volumes and parallel requests.

S3 is especially useful for backups. For example, in modern control panels like FASTPANEL, you can configure backups to be stored on S3-compatible services such as MinIO, making the process fully automatic.

How to get started with S3

You don’t have to use AWS specifically. MinIO and other S3-compatible solutions let you create your own storage on your own server or in a private cloud. This is especially relevant for those who want full control over their data and prefer to pay only for the server rather than for AWS cloud services.

All of these systems operate on the same principle: an object is stored in a “bucket” and is accessible via a unique link. The differences lie in the infrastructure and additional capabilities such as file versioning or encryption.

If you want to try setting up your own private S3 storage, you can install MinIO from our ready-made template on any VPS. You won’t even need to touch the console - within a couple of minutes, the MinIO web interface will be available in your browser.

Conclusion

S3 storage isn’t just a buzzword - it’s a practical tool that helps websites become more stable, secure, and scalable. AWS S3 remains the gold standard, but solutions like MinIO let you use the same approach on your own private server.

If you want your data to be safe and always at hand, S3 is definitely worth a try.

Why you need a VPN and why it’s better to have your own

· 2 min read
Customer Care Engineer

vpn-privacy-security-selfhosted-vps-encryption-anonymous-internet

A VPN stopped being something exotic long ago. Some people turn it on to use the internet safely while traveling, others to work from home with corporate services, and for many it’s simply a familiar way to stay safe online.

At its core, a VPN isn’t about “circumventing geo-blocking” so much as about control: you decide which server your traffic goes through and who can see it. That raises the question - what should you choose: a ready-made VPN service or your own server with a VPN?

What a VPN is in simple terms

A VPN (Virtual Private Network) is a “virtual tunnel” between you and the internet. All your traffic goes through this tunnel first and only then reaches the web.

Imagine you write a letter and put it in an envelope. Even if someone intercepts it on the way, they can’t read the contents without opening the envelope. A VPN works in much the same way: it encrypts your internet traffic.

Why you need it in real life

A VPN hasn’t been a geek toy for a long time. Here are a few scenarios most people run into:

  • Secure Wi-Fi. Connecting in a café, airport, or hotel is always risky: a network admin or an attacker can inspect your traffic. A VPN encrypts it and makes it useless to outsiders.

  • Bypassing blocks and censorship. Sometimes a site you need is simply unavailable due to ISP restrictions. A VPN solves that problem.

  • Remote work. Many companies use VPNs so employees can securely access internal resources from anywhere in the world.

  • Privacy. Your internet provider can see which websites you visit. Through a VPN, it sees only a connection to your server.

Why your own VPN beats a ready-made service

Most people pick polished VPN services with pretty apps. But there’s a catch: you’re trusting a third party with all your traffic. Many promise a “no-logs” policy, but there’s no way to verify that.

If you deploy a VPN on your own VPS, the picture changes:

  • You know exactly who controls the server (you do).

  • No outsiders have access to your data.

  • You can choose the server’s country and “change” your IP to whatever suits you.

  • You can tune the VPN to your needs: speed, protocols, per-user access restrictions, traffic controls.

It often works out cheaper, too: you can use a VPS not only for a VPN, but also, for example, for backups or hosting websites.

Is it hard to do?

Actually, no. Installing a VPN on a VPS takes just a couple of minutes because we’ve prepared preconfigured templates that are ready to go. WireGuard, OpenVPN, and 3X-UI (xray) with a graphical interface and browser-based management are available for installation on any of our VPS servers. No console commands required:

  1. Rent a server.

  2. Choose the template you need.

  3. Wait a couple of minutes.

  4. Receive your credentials by email.

  5. Open the link in your browser and start using it.

It’s that simple and fast.

Conclusion

A VPN is useful not only for IT professionals but for everyday users: for security, privacy, and freedom online. And a self-hosted VPN on a VPS takes you to a level where you control everything - from speed to confidentiality.

This approach inspires far more confidence than subscribing to an unknown VPN provider.

Start taking control of your privacy today with kodu.cloud.

Why n8n is the best process automation tool in 2025

· 2 min read
Customer Care Engineer

process-automation-n8n-workflows-2025

Business process automation isn’t just a trend - it’s a real necessity for companies that want to scale without unnecessary costs. And if you’re looking for a powerful, flexible, and affordable tool to automate workflows, n8n is a name you’ve likely already heard.


What is n8n?

n8n is an open-source low-code workflow automation platform that lets you create integrations between different services and automate tasks without deep technical knowledge.

Unlike proprietary solutions such as Zapier or Make, n8n offers:

  • full control over your infrastructure;

  • flexible business-logic configuration;

  • extensibility via custom nodes;

  • a free self-hosted model.

Thanks to its open-source (Fair Code) license and active community, n8n has quickly become the de facto standard among low-code automation tools.


  • Supports 400+ integrations and has a rapidly growing community.

  • Enables complex scenarios with conditional branches, loops, API requests, and variables.

  • Easy to launch on your own server or in the cloud.

  • Meets the demand for private, self-hosted solutions amid changing policies at major SaaS providers.

n8n is especially popular among developers, but non-technical specialists - from marketers to HR - are increasingly using it as a visual process editor.


What can you automate with n8n?

Key n8n use cases:

  • CRM and email-service integrations (e.g., HubSpot + Mailchimp)

  • Data collection via API and writing to databases or Google Sheets

  • Event monitoring (webhooks, schedules, events from Notion, GitHub, Slack)

  • Notifications and alerts in Telegram, Discord, Slack

  • Working with AI: integrations with OpenAI, Hugging Face, Replicate, and more


How to start using n8n quickly

A manual setup can take time - Docker, databases, Nginx - all of this requires experience with Linux and the command line. But there’s an easier path.

We offer a ready-made template with n8n preinstalled that you can deploy in just a few clicks.

No extra configuration, with an optimized setup and secure access. You get a ready-to-use environment and can immediately start building your first automations.


Conclusion

n8n is a powerful open-source engine for low-code automation that’s perfect for both personal use and large projects. It handles dozens of routine tasks, freeing you or your team from manual work and boosting efficiency.

And if you don’t want to spend time on installation, simply use our ready-made template for any VPS server. You’ll get a working solution in just a couple of minutes and can focus on what matters most - your workflows.

500 Internal Server Error: what causes it and how to fix it

· 5 min read
Customer Care Engineer

how-to-fix-500-internal-server-error-website-troubleshooting

A 500 Internal Server Error is one of the most common issues website owners and administrators encounter. It signals that something went wrong on the server—but offers no precise diagnostics. This article explains what typically triggers a 500 error and how you can resolve it.


Possible causes of a 500 error

A 500 error can arise for many reasons. The most frequent are:

  1. Server-side resource issues

Often error 500 can be caused by technical problems on the server, such as lack of resources (RAM, CPU time).

  1. Errors in the website code

Scripts or website code may contain errors that cause a crash. This can be due to incorrect requests, errors in configuration files, or problems with the interaction of site components.

  1. Problems with the .htaccess file

The .htaccess file is used to configure the web server and may contain errors that will result in a 500 error. For example, incorrect redirect rules or incorrect parameters can cause a crash.

  1. Recent updates

Errors can occur after updates to the website or server applications where changes were not handled correctly.


How to fix a 500 error

  1. Check the web-server logs

To determine the cause of a 500 error, the first step is to check the server logs. These logs typically contain information about the failure — whether it's a code error, misconfiguration, or a server-level issue. However, it's important to understand that web server logs (such as those from Nginx or Apache) often only record the occurrence of the error and the response code, not the root cause. This is especially true for Nginx, which usually acts as a proxy and simply forwards the error from the backend application.

Depending on the web server being used, logs may be located in the following directories:

Apache:

  • Ubuntu/Debian: /var/log/apache2/error.log

  • CentOS/AlmaLinux/Rocky Linux: /var/log/httpd/error.log

Nginx:

  • /var/log/nginx/error.log

If your server is managed through a control panel such as FASTPANEL, viewing logs becomes even easier. To do this:

  • Log in to the control panel.

  • Open the site card and locate the “Logs” section.

  • The “Frontend Error Log” tab contains Nginx web-server errors, while the “Backend Error Log” tab contains Apache errors.

Keep in mind that many CMSs and frameworks (WordPress, Laravel, Joomla, etc.) maintain their own error logs. These logs often provide more precise information about the cause of a 500 error. Consult your platform’s documentation to find where these logs are stored.

Logs will very likely give you detailed insight into what went wrong. If the 500 error is triggered by misconfiguration or code issues, you can see the files—or even the exact lines—that cause the failure.

  1. Enable PHP-side error logging

To obtain more detailed diagnostics, enable logging directly in PHP—especially useful when the error originates in code and does not appear in web-server logs.

To do this, set the following values in the php.ini file:

display_errors = Off

log_errors = On

error_log = /var/log/php_errors.log

Here:

  • display_errors — suppresses error output in the browser

  • log_errors — writes errors to a log file

  • error_log — path to the log file (PHP must have write permissions)

Typical  php.ini locations:

  • Debian/Ubuntu: /etc/php/*/apache2/php.ini or /etc/php/*/cli/php.ini

  • CentOS/AlmaLinux: /etc/php.ini

Or locate it via:

php -i | grep "php.ini"

In FASTPANEL: open the site card → “PHP Settings”, search for variables such as display_errors, change their values, and click "Save". 

  1. Check the .htaccess file

If the error appeared after editing .htaccess, revert the file to its previous state.

If you are not sure what exactly has changed, temporarily rename .htaccess (for example, to .htaccess.bak) - if the error disappears, then the problem is in this file.

In this case, try to restore the .htaccess file from a backup if available, or use the default .htaccess file for your CMS, which you can get from here.

  1. Verify file permissions and ownership

Improper permissions can trigger a 500. Ensure the site root and all sub-files have correct rights and owner:

ls -laR /path/to/your/site/root

Recommended permissions:

  • For directories: 755 — read, write, and execute for the owner; read and execute for everyone else.

  • For files: 644 — read and write for the owner; read-only for everyone else.

Ownership:

Files and folders should belong to the web-server user (e.g.,www-data or apache).

If necessary, you can adjust permissions and ownership by using the following commands:

  • Change to your site’s root directory:
cd /path/to/root/directory/site
  • Set the correct ownership and permissions:
sudo chown -R yoursiteuser:yoursiteuser . && sudo chmod 644 . -R && sudo chmod +X . -R

Please replace yoursiteuser with the actual user and group that own your site.

  1. Disable plugins and themes.

For CMS-driven sites such as WordPress, a 500 error often arises from plugin or theme conflicts. Disable all plugins and switch to a default theme to see whether this resolves the issue.

  1. Make sure the server has sufficient free resources for your sites.
  • Check that you have enough free disk space:
sudo df -h
  • Check that the server has not run out of inodes:
sudo df -ih
  • Check that the server has enough RAM:
sudo free -mh
  • Check current CPU load:
sudo ps -eo pid,ppid,cmd,%mem,%cpu --sort=-%cpu | head
  • Alternatively, open the process monitor with the command:
sudo top

If you have run out of disk space or inodes, you can identify which files and directories consume the most space by following the instructions in the relevant article.

If RAM or CPU load is excessively high, the causes may vary. Start your investigation by blocking search-engine bots, as they are frequently the source of elevated load. 

  1. Verify that the DBMS is healthy.

Most often this will be MySQL; below are several quick steps to check whether your databases are OK. 

  • Confirm that the MySQL service is running:
sudo systemctl status mysql
  • Check the MySQL error log:
sudo grep -i error /var/log/mysql/error.log
  • Check all databases for errors:
mysqlcheck -A -c

If errors are found, first be absolutely sure you have backups of the affected databases. If needed, create a dump with:

mysqldump -u [user] -p [database_name] > /path/to/file/dump.sql
  • Replace [user] with the MySQL username.

  • Replace [database_name] with the name of the database you want to export.

  • /path/to/dump.sql  is the path where the dump file will be saved.

After that, run the error correction procedure with mysqlcheck:

mysqlcheck -A --auto-repair -c
  1. Contact your hosting provider.

If you are unable to identify the problem, it may be worth contacting your hosting provider's support. It can help identify problems on the server that are not visible at the user level. You can learn about how to choose the right hosting provider in this article.


Conclusion

Error 500 is not a verdict for your website. With the help of basic diagnostic tools, you can quickly find out the cause and fix the problem. If you are not confident in your abilities, you can always turn to specialists. It is important to remember that error 500 found and fixed in time will help you avoid more serious problems in the future.

404 Not Found error in Joomla: Causes and how to fix it

· 3 min read
Customer Care Engineer

Joomla-404-Not-Found-error-message-and-how-to-fix-broken-links-or-missing-pages-step-by-step

Error 404 Not Found on the site running on Joomla means that the requested page can not be found. Visitors often get to it from search engines, outdated links, or even from the menu of the site itself. The reasons can range from a deleted article to routing issues.

In this article, we will explain why error 404 appears in Joomla and how to quickly eliminate it.


What 404 means in Joomla

A 404 error is the standard response from the server when it cannot find the requested page. In Joomla, this usually happens because:

  • The content item has been deleted or unpublished.

  • The menu structure is broken.

  • The component responsible for rendering the page is not working.


Primary causes of a 404 error in Joomla

  1. Content deleted or unpublished

If an article, category, or component is removed but a menu or module still links to it, Joomla returns a 404.

  1. Menu item points to a non-existent target

Even if the article exists, a menu item linked to a missing category or incorrect route will trigger the error.

  1. SEF (search-engine-friendly) URL problems

Joomla’s SEF links depend on component routes. After a site migration, enabling SEO, or changing aliases, “broken” links can appear.

  1. Errors during site migration

Moving to a new domain or host can change the URL structure and make some pages inaccessible.

  1. Component missing in the URL

For example, a link like index.php?option=com_k2&view=item&id=1 will fail if the K2 component is not installed.


How to fix a 404 in Joomla

  1. Check the menu item

Go to MenuMain Menu and verify that the item points to an existing article, category, or component. Re-create the menu item if needed.

  1. Regenerate SEF URLs
  • In the admin panel, open Global ConfigurationSiteSEO Settings.

  • Confirm that SEF URLs and URL rewriting are enabled.

  • Clear the cache: SystemClear Cache.

  • If you use an extension like sh404SEF or JoomSEF, update or reset its SEF table.

  1. Check .htaccess

To enable SEF (Search Engine Friendly) URLs, URL rewriting must be active. Make sure the .htaccess file is present in your site's root directory and that it includes the following directives to enable mod_rewrite:

RewriteEngine On

RewriteBase /

For more information about the default .htaccess structure in Joomla, check out this article.

  1. Enable error reporting

For debugging, turn on error output:

  • Go to SystemGeneral SettingsServer.

  • Set Error Messages: Maximum.

Joomla will then show a more detailed message that will help you find the cause of the error.

  1. Look at the error log

Most hosters provide an Apache error log. Look for lines with a 404 status code to identify problematic URLs.

If you use FASTPANEL: open the site card → Logs → check Backend access log and Frontend access log tabs to see which address triggered the 404 and the request origin. This helps trace broken links.

  1. Configure redirects

If the page was deleted, but you want to keep traffic from external links, create a redirect. In Joomla it is done through the component “Redirects”:

  • Navigate to ComponentsRedirects.

  • Enable it in Options if disabled.

  • Add the old URL and the new path (without the domain).


How to prevent 404 errors in the future

  • Do not delete content without adding a redirect.

  • Audit menus after moving or unpublishing items.

  • Use the Redirects component or .htaccess to manage redirections.

  • Monitor 404 errors via Google Search Console.


Conclusion

Error 404 in Joomla - a common phenomenon, but quite solvable. In most cases, it can be eliminated in 5-10 minutes by checking the menu, SEF settings, and site structure. Adding a reliable redirect system eliminates the risk of traffic loss on remote pages.

Error 404 in WordPress: where it comes from and how to fix it

· 3 min read
Customer Care Engineer

how-to-fix-404-page-not-found-error-in-WordPress

Error 404 Not Found is one of the most common problems WordPress site owners face. This message appears when the server cannot find the page requested by the user. The reason could be due to link settings, deleted content, or even the theme of the site.

If not handled properly, the error can affect visitor behavior and search rankings. In this article, briefly and to the point, we will tell you why a 404 error appears in WordPress and how to fix it.


What the 404 error means in WordPress

When someone visits your site, WordPress tries to match the URL with records in the database. If the page is not found, a 404 error is displayed. This is not a server failure; the site continues to work, but the specific page is missing.


Why does 404 occur in WordPress

  1. Permalink structure is broken

One of the most common reasons. For example, you changed the URL structure in Settings → Permalinks, but WordPress did not update the .htaccess file. In this case, all pages except the homepage will return a 404 error. To fix this, you can manually update the .htaccess file or re-save the permalink settings in the WordPress admin panel - this will force WordPress to regenerate the .htaccess file with the correct rules.

  1. Page or post deleted

If you have deleted a post or page, but the links to it remain (for example, in the menu, in search, or on other sites), visitors will be taken to a 404 page.

  1. .htaccess file corrupted

This file is responsible for URL routing. If it is accidentally deleted or damaged, WordPress cannot process addresses correctly.

  1. Theme or plugin issues

Sometimes, a 404 error appears after installing or updating a plugin, especially if it works with URLs, routes, custom post types, etc. There can also be errors in the theme’s functions.php file.


How to fix a 404 error in WordPress

1. Regenerate permalinks

Go to Admin → Settings → Permalinks → simply click “Save Changes”. Even if you don’t change anything, WordPress will rebuild the structure and update .htaccess.

2. Check the .htaccess file

Connect to the site via FTP or through the file manager in the control panel. Find the .htaccess file in the WordPress root. Its basic content:

# BEGIN WordPress

<IfModule mod_rewrite.c>

RewriteEngine On

RewriteBase /

RewriteRule ^index\.php$ - [L]

RewriteCond %{REQUEST_FILENAME} !-f

RewriteCond %{REQUEST_FILENAME} !-d

RewriteRule . /index.php [L]

</IfModule>

# END WordPress

If it is missing, create it and paste this code manually. Make sure the file has 644 permissions. For more information about the default .htaccess structure in WordPress, check out this article.

3. Disable suspicious plugins

If the error appeared after installing a plugin, temporarily disable it. If a custom post type is used (for example, WooCommerce products or a portfolio), make sure the plugin correctly registers routes.

4. Try a default theme

Sometimes, a 404 error is caused by errors in a custom theme. Switch to a default WordPress theme (for example, Twenty Twenty-Four) and check whether the error disappears.

5. Enable error logging

For debugging purposes, you can enable WordPress error output. Add the following lines to wp-config.php:

define( 'WP_DEBUG', true );

define( 'WP_DEBUG_LOG', true );

After that, errors will be saved to the /wp-content/debug.log.

6. Check server logs

If you use FASTPANEL, open the site card → “Logs” section → check the “Backend access log” and “Frontend access log” tabs. There, you can see which address caused the 404 error and where the request came from. This helps find “broken” links.


How to prevent 404s in the future

  • Do not change the link structure unless necessary.

  • Use the Redirection plugin to set up 301 redirects.

  • After deleting pages, update the menus and links.

  • Periodically check 404 errors in Google Search Console.


Conclusion

A 404 error in WordPress is unpleasant but solvable. Most often, simply saving the link structure or editing .htaccess helps. And if the problem lies deeper, the server log or debug file will help you locate it quickly.

By fixing 404s, you not only improve the site experience but also help its search promotion.

Examples of .htaccess for popular CMSs: how to restore the default file

· 13 min read
Customer Care Engineer

default-.htaccess-file-examples-for-WordPress-Joomla-Drupal-and-other-CMS-with-code-snippets

The .htaccess file is a configuration file used on Apache web servers to manage website settings without access to the server’s main configuration. With it, you can enable redirects, restrict access, configure SEO-friendly URLs, set up caching, and much more—directly from the root of your site or any of its directories.

Many CMSs automatically create this file on installation or include a sample in the distribution.

If you're working with hosting, especially on Apache, it's important to know what the default .htaccess looks like for different CMSs. This helps you:

  • Check that everything is correct after installation;

  • Restore the file if it was accidentally deleted;

  • Understand what rules the system uses “out of the box”.


Where .htaccess is located

The .htaccess file is usually found in the site’s root folder, for example:

/var/www/site.com/public_html/.htaccess

If the file is missing (e.g., it was accidentally deleted), you can create it manually with the name .htaccess (name begins with a dot, no extension).

Open the file with a text editor (e.g., Notepad++ or VS Code).

warning

Do not use office suites (such as MS Word) for editing, as they may insert hidden characters that will break the file.

Below is a collection of standard .htaccess files used by default in popular CMSs. These examples can come in handy if you accidentally deleted or corrupted the original .htaccess file and need to restore it for your site to work properly.


Wordpress

The default .htaccess for WordPress enables “clean” URLs and includes basic redirect rules:

# BEGIN WordPress

<IfModule mod_rewrite.c>

RewriteEngine On

RewriteBase /

RewriteRule ^index\.php$ - [L]

RewriteCond %{REQUEST_FILENAME} !-f

RewriteCond %{REQUEST_FILENAME} !-d

RewriteRule . /index.php [L]

</IfModule>

# END WordPress

If a multisite with subdomains is used (e.g., site1.example.com, site2.example.com):

# BEGIN WordPress Multisite

<IfModule mod_rewrite.c>

RewriteEngine On

RewriteBase /

RewriteRule ^index\.php$ - [L]



# Redirect for multisite (subdomains)

RewriteCond %{REQUEST_FILENAME} -f [OR]

RewriteCond %{REQUEST_FILENAME} -d

RewriteRule ^ - [L]

RewriteRule ^(wp-(content|admin|includes).*) $1 [L]

RewriteRule ^(.*\.php)$ $1 [L]

RewriteRule . index.php [L]

</IfModule>

# END WordPress Multisite

If a multisite with subdirectories is used (e.g., example.com/site1, example.com/site2):

# BEGIN WordPress Multisite

<IfModule mod_rewrite.c>

RewriteEngine On

RewriteBase /

RewriteRule ^index\.php$ - [L]



# Redirect for multisite (subdirectories)

RewriteCond %{REQUEST_FILENAME} -f [OR]

RewriteCond %{REQUEST_FILENAME} -d

RewriteRule ^ - [L]

RewriteRule . index.php [L]

</IfModule>

# END WordPress Multisite

Joomla 2.5-3

Joomla uses .htaccess for basic protection and SEF configuration:

##

# @package Joomla

# @copyright Copyright (C) 2005 - 2012 Open Source Matters. All rights reserved.

# @license GNU General Public License version 2 or later; see LICENSE.txt

##



##

# READ THIS COMPLETELY IF YOU CHOOSE TO USE THIS FILE!

#

# The line just below this section: 'Options +FollowSymLinks' may cause problems

# with some server configurations. It is required for use of mod_rewrite, but may already

# be set by your server administrator in a way that dissallows changing it in

# your .htaccess file. If using it causes your server to error out, comment it out (add # to

# beginning of line), reload your site in your browser and test your sef url's. If they work,

# it has been set by your server administrator and you do not need it set here.

##



## Can be commented out if causes errors, see notes above.

Options +FollowSymLinks



## Mod_rewrite in use.



RewriteEngine On



## Begin - Rewrite rules to block out some common exploits.

# If you experience problems on your site block out the operations listed below

# This attempts to block the most common type of exploit `attempts` to Joomla!

#

# Block out any script trying to base64_encode data within the URL.

RewriteCond %{QUERY_STRING} base64_encode[^(]*\([^)]*\) [OR]

# Block out any script that includes a <script> tag in URL.

RewriteCond %{QUERY_STRING} (<|%3C)([^s]*s)+cript.*(>|%3E) [NC,OR]

# Block out any script trying to set a PHP GLOBALS variable via URL.

RewriteCond %{QUERY_STRING} GLOBALS(=|\[|\%[0-9A-Z]{0,2}) [OR]

# Block out any script trying to modify a _REQUEST variable via URL.

RewriteCond %{QUERY_STRING} _REQUEST(=|\[|\%[0-9A-Z]{0,2})

# Return 403 Forbidden header and show the content of the root homepage

RewriteRule .* index.php [F]

#

## End - Rewrite rules to block out some common exploits.



## Begin - Custom redirects

#

# If you need to redirect some pages, or set a canonical non-www to

# www redirect (or vice versa), place that code here. Ensure those

# redirects use the correct RewriteRule syntax and the [R=301,L] flags.

#

## End - Custom redirects



##

# Uncomment following line if your webserver's URL

# is not directly related to physical file paths.

# Update Your Joomla! Directory (just / for root).

##



# RewriteBase /



## Begin - Joomla! core SEF Section.

#

RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

#

# If the requested path and file is not /index.php and the request

# has not already been internally rewritten to the index.php script

RewriteCond %{REQUEST_URI} !^/index\.php

# and the request is for something within the component folder,

# or for the site root, or for an extensionless URL, or the

# requested URL ends with one of the listed extensions

RewriteCond %{REQUEST_URI} /component/|(/[^.]*|\.(php|html?|feed|pdf|vcf|raw))$ [NC]

# and the requested path and file doesn't directly match a physical file

RewriteCond %{REQUEST_FILENAME} !-f

# and the requested path and file doesn't directly match a physical folder

RewriteCond %{REQUEST_FILENAME} !-d

# internally rewrite the request to the index.php script

RewriteRule .* index.php [L]

#

## End - Joomla! core SEF Section.

Joomla 4-5

In Joomla 4 more attention is paid to security and caching:

##

# @package    Joomla

# @copyright  (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>

# @license    GNU General Public License version 2 or later; see LICENSE.txt

##



##

# READ THIS COMPLETELY IF YOU CHOOSE TO USE THIS FILE!

#

# The line 'Options +FollowSymLinks' may cause problems with some server configurations.

# It is required for the use of Apache mod_rewrite, but it may have already been set by

# your server administrator in a way that disallows changing it in this .htaccess file.

# If using it causes your site to produce an error, comment it out (add # to the

# beginning of the line), reload your site in your browser and test your sef urls. If

# they work, then it has been set by your server administrator and you do not need to

# set it here.

##



## MISSING CSS OR JAVASCRIPT ERRORS

#

# If your site looks strange after enabling this file, then your server is probably already

# gzipping css and js files and you should comment out the GZIP section of this file.

##



## OPENLITESPEED

#

# If you are using an OpenLiteSpeed web server then any changes made to this file will

# not take effect until you have restarted the web server.

##



## Can be commented out if causes errors, see notes above.

Options +FollowSymlinks

Options -Indexes



## No directory listings

<IfModule mod_autoindex.c>

IndexIgnore *

</IfModule>



## Suppress mime type detection in browsers for unknown types

<IfModule mod_headers.c>

Header always set X-Content-Type-Options "nosniff"

</IfModule>



## Protect against certain cross-origin requests. More information can be found here:

## https://developer.mozilla.org/en-US/docs/Web/HTTP/Cross-Origin_Resource_Policy_(CORP)

## https://web.dev/why-coop-coep/

#<IfModule mod_headers.c>

# Header always set Cross-Origin-Resource-Policy "same-origin"

# Header always set Cross-Origin-Embedder-Policy "require-corp"

#</IfModule>



## Disable inline JavaScript when directly opening SVG files or embedding them with the object-tag

<FilesMatch "\.svg$">

  <IfModule mod_headers.c>

    Header always set Content-Security-Policy "script-src 'none'"

  </IfModule>

</FilesMatch>



## These directives are only enabled if the Apache mod_rewrite module is enabled

<IfModule mod_rewrite.c>

RewriteEngine On



## Begin - Rewrite rules to block out some common exploits.

# If you experience problems on your site then comment out the operations listed

# below by adding a # to the beginning of the line.

# This attempts to block the most common type of exploit `attempts` on Joomla!

#

# Block any script trying to base64_encode data within the URL.

RewriteCond %{QUERY_STRING} base64_encode[^(]*\([^)]*\) [OR]

# Block any script that includes a <script> tag in URL.

RewriteCond %{QUERY_STRING} (<|%3C)([^s]*s)+cript.*(>|%3E) [NC,OR]

# Block any script trying to set a PHP GLOBALS variable via URL.

RewriteCond %{QUERY_STRING} GLOBALS(=|\[|\%[0-9A-Z]{0,2}) [OR]

# Block any script trying to modify a _REQUEST variable via URL.

RewriteCond %{QUERY_STRING} _REQUEST(=|\[|\%[0-9A-Z]{0,2})

# Return 403 Forbidden header and show the content of the root home page

RewriteRule .* index.php [F]

#

## End - Rewrite rules to block out some common exploits.



## Begin - Custom redirects

#

# If you need to redirect some pages, or set a canonical non-www to

# www redirect (or vice versa), place that code here. Ensure those

# redirects use the correct RewriteRule syntax and the [R=301,L] flags.

#

## End - Custom redirects



##

# Uncomment the following line if your webserver's URL

# is not directly related to physical file paths.

# Update Your Joomla! Directory (just / for root).

##



# RewriteBase /



## Begin - Joomla! core SEF Section.

#

# PHP FastCGI fix for HTTP Authorization, required for the API application

RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

# -- SEF URLs for the API application

# If the requested path starts with /api, the file is not /api/index.php

# and the request has not already been internally rewritten to the

# api/index.php script

RewriteCond %{REQUEST_URI} ^/api/

RewriteCond %{REQUEST_URI} !^/api/index\.php

# and the requested path and file doesn't directly match a physical file

RewriteCond %{REQUEST_FILENAME} !-f

# and the requested path and file doesn't directly match a physical folder

RewriteCond %{REQUEST_FILENAME} !-d

# internally rewrite the request to the /api/index.php script

RewriteRule .* api/index.php [L]

# -- SEF URLs for the public frontend application

# If the requested path and file is not /index.php and the request

# has not already been internally rewritten to the index.php script

RewriteCond %{REQUEST_URI} !^/index\.php

# and the requested path and file doesn't directly match a physical file

RewriteCond %{REQUEST_FILENAME} !-f

# and the requested path and file doesn't directly match a physical folder

RewriteCond %{REQUEST_FILENAME} !-d

# internally rewrite the request to the index.php script

RewriteRule .* index.php [L]

#

## End - Joomla! core SEF Section.

</IfModule>



## These directives are only enabled if the Apache mod_rewrite module is disabled

<IfModule !mod_rewrite.c>

<IfModule mod_alias.c>

# When Apache mod_rewrite is not available, we instruct a temporary redirect

# of the start page to the front controller explicitly so that the website

# and the generated links can still be used.

RedirectMatch 302 ^/$ /index.php/

# RedirectTemp cannot be used instead

</IfModule>

</IfModule>



## GZIP

## These directives are only enabled if the Apache mod_headers module is enabled.

## This section will check if a .gz file exists and if so will stream it

##     directly or fallback to gzip any asset on the fly

## If your site starts to look strange after enabling this file, and you see

##     ERR_CONTENT_DECODING_FAILED in your browser console network tab,

##     then your server is already gzipping css and js files and you don't need this

##     block enabled in your .htaccess

<IfModule mod_headers.c>

# Serve gzip compressed CSS files if they exist

# and the client accepts gzip.

RewriteCond "%{HTTP:Accept-encoding}" "gzip"

RewriteCond "%{REQUEST_FILENAME}\.gz" -s

RewriteRule "^(.*)\.css" "$1\.css\.gz" [QSA]



# Serve gzip compressed JS files if they exist

# and the client accepts gzip.

RewriteCond "%{HTTP:Accept-encoding}" "gzip"

RewriteCond "%{REQUEST_FILENAME}\.gz" -s

RewriteRule "^(.*)\.js" "$1\.js\.gz" [QSA]



# Serve correct content types, and prevent mod_deflate double gzip.

RewriteRule "\.css\.gz$" "-" [T=text/css,E=no-gzip:1]

RewriteRule "\.js\.gz$" "-" [T=text/javascript,E=no-gzip:1]



<FilesMatch "(\.js\.gz|\.css\.gz)$">

# Serve correct encoding type.

Header set Content-Encoding gzip



# Force proxies to cache gzipped &

# non-gzipped css/js files separately.

Header append Vary Accept-Encoding

</FilesMatch>

</IfModule>

Drupal 7

The .htaccess in Drupal 7 includes basic security and optimization settings. Typical content:

# Use the following to prevent server signatures and directory browsing

ServerSignature Off

Options -Indexes



# Protect sensitive files

<FilesMatch "\.(htaccess|htpasswd)">

  Order Allow,Deny

  Deny from all

</FilesMatch>



# Protect files from being accessed directly

<FilesMatch "\.(txt|md|yml|json|xml)$">

  Order Allow,Deny

  Deny from all

</FilesMatch>



# Set a default timezone for PHP

SetEnv TZ Europe/Amsterdam



# Enable compression for better performance

AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css application/x-javascript text/javascript application/javascript



# Cache settings for better performance

<IfModule mod_headers.c>

  Header set Cache-Control "public, max-age=3600"

</IfModule>

Drupal 8

For Drupal 8, .htaccess already includes additional improvements and supports new features. For example, there is HTTP/2 support, improved security, customization to handle clean URLs and caching.

# Prevent server signature and directory browsing

ServerSignature Off

Options -Indexes



# Protect sensitive files

<FilesMatch "\.(htaccess|htpasswd|ini|log|conf)$">

  Order Allow,Deny

  Deny from all

</FilesMatch>



# Clean URLs support

RewriteEngine on

RewriteBase /



# Support for HTTP/2

<IfModule http2_module>

  Protocols h2 http/1.1

</IfModule>



# Cache control for assets

<IfModule mod_headers.c>

  Header set Cache-Control "public, max-age=86400, s-maxage=86400, must-revalidate"

</IfModule>



# Enable compression

AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css application/x-javascript application/javascript text/javascript



# Redirect trailing slashes for clean URLs

RewriteCond %{REQUEST_FILENAME} !-d

RewriteCond %{REQUEST_URI} /+$

RewriteRule ^(.*)/$ /$1 [R=301,L]

Drupal 9

For Drupal 9 the .htaccess includes further enhancements for working with newer web technologies such as HTTP/2 support and stricter security measures.

# Prevent directory browsing and server signatures

ServerSignature Off

Options -Indexes



# Protect sensitive files

<FilesMatch "\.(htaccess|htpasswd|ini|log|conf)$">

  Order Allow,Deny

  Deny from all

</FilesMatch>



# Enable clean URLs (this is essential for Drupal to work properly)

RewriteEngine on

RewriteBase /



# Support for HTTP/2 and modern caching

<IfModule mod_http2.c>

  Protocols h2 http/1.1

</IfModule>



<IfModule mod_headers.c>

  Header set Cache-Control "public, max-age=86400, s-maxage=86400, must-revalidate"

</IfModule>



# Enable Gzip compression

AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css application/x-javascript text/javascript application/javascript



# Clean URLs support for Drupal

RewriteCond %{REQUEST_FILENAME} !-d

RewriteCond %{REQUEST_URI} /+$

RewriteRule ^(.*)/$ /$1 [R=301,L]

OpenCart

Options +FollowSymlinks

RewriteEngine On

RewriteBase /

RewriteCond %{REQUEST_FILENAME} !-f

RewriteCond %{REQUEST_FILENAME} !-d

RewriteRule ^([^?]*) index.php?_route_=$1 [L,QSA]

Magento (2.x)

Magento has a complex .htaccess that includes rules for compression, caching, and security. Example for Magento 2:

<IfModule mod_php5.c>

php_flag memory_limit 756M

php_flag max_execution_time 18000

</IfModule>



<IfModule mod_rewrite.c>

Options +FollowSymLinks

RewriteEngine on



RewriteCond %{REQUEST_URI} !^/pub/

RewriteRule ^(.*)$ pub/$1 [L]

</IfModule>

PrestaShop (1.7.x)

PrestaShop automatically generates the .htaccess file during installation or when you change SEO-friendly URL settings.

# ~~start~~ Do not remove this comment, Prestashop will keep automatically the code outside this comment when .htaccess will be generated again

# .htaccess automatically generated by PrestaShop e-commerce open-source solution

# http://www.prestashop.com - http://www.prestashop.com/forums



<IfModule mod_rewrite.c>

<IfModule mod_env.c>

     SetEnv HTTP_MOD_REWRITE On

</IfModule>



RewriteEngine on



# Domain: www.example.com

RewriteRule . - [E=REWRITEBASE:/]



# API

RewriteRule ^api$ api/ [L]

RewriteRule ^api/(.*)$ %{ENV:REWRITEBASE}webservice/dispatcher.php?url=$1 [QSA,L]



# Images

RewriteRule ^([0-9])(\-[_a-zA-Z0-9-]*)?(-[0-9]+)?/.+\.jpg$ %{ENV:REWRITEBASE}img/p/$1/$1$2$3.jpg [L]

RewriteRule ^([0-9])([0-9])(\-[_a-zA-Z0-9-]*)?(-[0-9]+)?/.+\.jpg$ %{ENV:REWRITEBASE}img/p/$1/$2/$1$2$3$4.jpg [L]

RewriteRule ^([0-9])([0-9])([0-9])(\-[_a-zA-Z0-9-]*)?(-[0-9]+)?/.+\.jpg$ %{ENV:REWRITEBASE}img/p/$1/$2/$3/$1$2$3$4$5.jpg [L]

RewriteRule ^([0-9])([0-9])([0-9])([0-9])(\-[_a-zA-Z0-9-]*)?(-[0-9]+)?/.+\.jpg$ %{ENV:REWRITEBASE}img/p/$1/$2/$3/$4/$1$2$3$4$5$6.jpg [L]

RewriteRule ^([0-9])([0-9])([0-9])([0-9])([0-9])(\-[_a-zA-Z0-9-]*)?(-[0-9]+)?/.+\.jpg$ %{ENV:REWRITEBASE}img/p/$1/$2/$3/$4/$5/$1$2$3$4$5$6$7.jpg [L]

RewriteRule ^([0-9])([0-9])([0-9])([0-9])([0-9])([0-9])(\-[_a-zA-Z0-9-]*)?(-[0-9]+)?/.+\.jpg$ %{ENV:REWRITEBASE}img/p/$1/$2/$3/$4/$5/$6/$1$2$3$4$5$6$7$8.jpg [L]

RewriteRule ^([0-9])([0-9])([0-9])([0-9])([0-9])([0-9])([0-9])(\-[_a-zA-Z0-9-]*)?(-[0-9]+)?/.+\.jpg$ %{ENV:REWRITEBASE}img/p/$1/$2/$3/$4/$5/$6/$7/$1$2$3$4$5$6$7$8$9.jpg [L]

RewriteRule ^c/([0-9]+)(\-[\.*_a-zA-Z0-9-]*)(-[0-9]+)?/.+\.jpg$ %{ENV:REWRITEBASE}img/c/$1$2$3.jpg [L]

RewriteRule ^c/([a-zA-Z_-]+)(-[0-9]+)?/.+\.jpg$ %{ENV:REWRITEBASE}img/c/$1$2.jpg [L]



# AlphaImageLoader for IE and fancybox

RewriteRule ^images_ie/?([^/]+)\.(jpe?g|png|gif)$ js/jquery/plugins/fancybox/images/$1.$2 [L]



# Dispatcher

RewriteCond %{REQUEST_FILENAME} -s [OR]

RewriteCond %{REQUEST_FILENAME} -l [OR]

RewriteCond %{REQUEST_FILENAME} -d

RewriteRule ^.*$ - [NC,L]

RewriteRule ^.*$ %{ENV:REWRITEBASE}index.php [NC,L]

</IfModule>



<IfModule mod_headers.c>

<FilesMatch "\.(ttf|ttc|otf|eot|woff|woff2|svg)$">

     Header set Access-Control-Allow-Origin "*"

</FilesMatch>

</IfModule>



<IfModule mod_expires.c>

ExpiresActive On

ExpiresByType image/gif "access plus 1 month"

ExpiresByType image/jpeg "access plus 1 month"

ExpiresByType image/png "access plus 1 month"

ExpiresByType text/css "access plus 1 week"

ExpiresByType text/javascript "access plus 1 week"

ExpiresByType application/javascript "access plus 1 week"

ExpiresByType application/x-javascript "access plus 1 week"

ExpiresByType image/x-icon "access plus 1 year"

ExpiresByType image/svg+xml "access plus 1 year"

ExpiresByType image/vnd.microsoft.icon "access plus 1 year"

ExpiresByType application/font-woff "access plus 1 year"

ExpiresByType application/x-font-woff "access plus 1 year"

ExpiresByType font/woff2 "access plus 1 year"

ExpiresByType application/vnd.ms-fontobject "access plus 1 year"

ExpiresByType font/opentype "access plus 1 year"

ExpiresByType font/ttf "access plus 1 year"

ExpiresByType font/otf "access plus 1 year"

ExpiresByType application/x-font-ttf "access plus 1 year"

ExpiresByType application/x-font-otf "access plus 1 year"

</IfModule>



<IfModule mod_headers.c>

Header unset Etag

</IfModule>

FileETag none



<IfModule mod_deflate.c>

<IfModule mod_filter.c>

     AddOutputFilterByType DEFLATE text/html text/css text/javascript application/javascript application/x-javascript font/ttf application/x-font-ttf font/otf application/x-font-otf font/opentype image/svg+xml

</IfModule>

</IfModule>



# If rewrite mod isn't enabled

ErrorDocument 404 /index.php?controller=404



# ~~start~~ Do not remove this comment, Prestashop will keep automatically the code outside this comment when .htaccess will be generated again

# .htaccess automatically generated by PrestaShop e-commerce open-source solution

# http://www.prestashop.com - http://www.prestashop.com/forums



<IfModule mod_rewrite.c>

<IfModule mod_env.c>

     SetEnv HTTP_MOD_REWRITE On

</IfModule>



RewriteEngine on



# Domain: www.example.com

RewriteRule . - [E=REWRITEBASE:/]



# API

RewriteRule ^api$ api/ [L]

RewriteRule ^api/(.*)$ %{ENV:REWRITEBASE}webservice/dispatcher.php?url=$1 [QSA,L]



# Images

RewriteRule ^([0-9])(\-[_a-zA-Z0-9-]*)?(-[0-9]+)?/.+\.jpg$ %{ENV:REWRITEBASE}img/p/$1/$1$2$3.jpg [L]

RewriteRule ^([0-9])([0-9])(\-[_a-zA-Z0-9-]*)?(-[0-9]+)?/.+\.jpg$ %{ENV:REWRITEBASE}img/p/$1/$2/$1$2$3$4.jpg [L]

RewriteRule ^([0-9])([0-9])([0-9])(\-[_a-zA-Z0-9-]*)?(-[0-9]+)?/.+\.jpg$ %{ENV:REWRITEBASE}img/p/$1/$2/$3/$1$2$3$4$5.jpg [L]

RewriteRule ^([0-9])([0-9])([0-9])([0-9])(\-[_a-zA-Z0-9-]*)?(-[0-9]+)?/.+\.jpg$ %{ENV:REWRITEBASE}img/p/$1/$2/$3/$4/$1$2$3$4$5$6.jpg [L]

RewriteRule ^([0-9])([0-9])([0-9])([0-9])([0-9])(\-[_a-zA-Z0-9-]*)?(-[0-9]+)?/.+\.jpg$ %{ENV:REWRITEBASE}img/p/$1/$2/$3/$4/$5/$1$2$3$4$5$6$7.jpg [L]

RewriteRule ^([0-9])([0-9])([0-9])([0-9])([0-9])([0-9])(\-[_a-zA-Z0-9-]*)?(-[0-9]+)?/.+\.jpg$ %{ENV:REWRITEBASE}img/p/$1/$2/$3/$4/$5/$6/$1$2$3$4$5$6$7$8.jpg [L]

RewriteRule ^([0-9])([0-9])([0-9])([0-9])([0-9])([0-9])([0-9])(\-[_a-zA-Z0-9-]*)?(-[0-9]+)?/.+\.jpg$ %{ENV:REWRITEBASE}img/p/$1/$2/$3/$4/$5/$6/$7/$1$2$3$4$5$6$7$8$9.jpg [L]

RewriteRule ^c/([0-9]+)(\-[\.*_a-zA-Z0-9-]*)(-[0-9]+)?/.+\.jpg$ %{ENV:REWRITEBASE}img/c/$1$2$3.jpg [L]

RewriteRule ^c/([a-zA-Z_-]+)(-[0-9]+)?/.+\.jpg$ %{ENV:REWRITEBASE}img/c/$1$2.jpg [L]



# AlphaImageLoader for IE and fancybox

RewriteRule ^images_ie/?([^/]+)\.(jpe?g|png|gif)$ js/jquery/plugins/fancybox/images/$1.$2 [L]



# Dispatcher

RewriteCond %{REQUEST_FILENAME} -s [OR]

RewriteCond %{REQUEST_FILENAME} -l [OR]

RewriteCond %{REQUEST_FILENAME} -d

RewriteRule ^.*$ - [NC,L]

RewriteRule ^.*$ %{ENV:REWRITEBASE}index.php [NC,L]

</IfModule>



<IfModule mod_headers.c>

<FilesMatch "\.(ttf|ttc|otf|eot|woff|woff2|svg)$">

     Header set Access-Control-Allow-Origin "*"

</FilesMatch>

</IfModule>



<IfModule mod_expires.c>

ExpiresActive On

ExpiresByType image/gif "access plus 1 month"

ExpiresByType image/jpeg "access plus 1 month"

ExpiresByType image/png "access plus 1 month"

ExpiresByType text/css "access plus 1 week"

ExpiresByType text/javascript "access plus 1 week"

ExpiresByType application/javascript "access plus 1 week"

ExpiresByType application/x-javascript "access plus 1 week"

ExpiresByType image/x-icon "access plus 1 year"

ExpiresByType image/svg+xml "access plus 1 year"

ExpiresByType image/vnd.microsoft.icon "access plus 1 year"

ExpiresByType application/font-woff "access plus 1 year"

ExpiresByType application/x-font-woff "access plus 1 year"

ExpiresByType font/woff2 "access plus 1 year"

ExpiresByType application/vnd.ms-fontobject "access plus 1 year"

ExpiresByType font/opentype "access plus 1 year"

ExpiresByType font/ttf "access plus 1 year"

ExpiresByType font/otf "access plus 1 year"

ExpiresByType application/x-font-ttf "access plus 1 year"

ExpiresByType application/x-font-otf "access plus 1 year"

</IfModule>



<IfModule mod_headers.c>

Header unset Etag

</IfModule>

FileETag none



<IfModule mod_deflate.c>

<IfModule mod_filter.c>

     AddOutputFilterByType DEFLATE text/html text/css text/javascript application/javascript application/x-javascript font/ttf application/x-font-ttf font/otf application/x-font-otf font/opentype image/svg+xml

</IfModule>

</IfModule>



# If rewrite mod isn't enabled

ErrorDocument 404 /index.php?controller=404

Shopify, Squarespace, Adobe Commerce, and other cloud platforms

Shopify, Squarespace, and Adobe Commerce (formerly Magento Commerce) are cloud-based platforms that do not provide direct access to the .htaccess file. All configuration is done through the administrative panel.

Other examples of such services include Wix, Weebly, BigCommerce, and Jimdo. These platforms allow users to configure and optimize their websites through visual interfaces, without the need to manually edit server configuration files.

Need help restoring your .htaccess file?

If you're not sure which CMS your website is using or how to safely restore a broken .htaccess file, we're here to help.

Our technical support is completely free for all Kodu.cloud clients and available 24/7/365. Simply create a support ticket, and our team will assist you in a minutes.

For more details about what’s included, see our support policy.