Migrate a XenForo forum to Discourse

Hey guys so after trying for a week or so I decided to import the database on docker app which I hoped it will succeed without any issues. TBH I got some issues at first but it turned to be all easy to solve.

Now I would recommend doing this only once before you deploy your production site, after importing everything take a Discourse backup, wipe everything out and install new Discourse instance then restore the imported db.

Ok let’s begin:

  1. First lets backup your old database from your old server, sign in to your Discourse server then:

     ssh USER@XENFORO_SERVER_IP  # Use -p argument to specify custom port
     mysqldump -u XENFORO_DATABASE_USER -p XENFORO_DATABASE_NAME > xen_db.sql
  2. After installing Discourse start the app:

     ./launcher start app
  3. Now lets pull the database backup from xenforo server:

     scp USER@XENFORO_SERVER_IP:~/xen_db.sql /var/discourse/shared/standalone/xen_db.sql

    If you changed the ssh port use -P argument

     scp -P 2222 USER@XENFORO_SERVER_IP:~/xen_db.sql /var/discourse/shared/standalone/xen_db.sql 
  4. Login to docker machine, install and start mysql:

     docker exec -it app bash
     sudo apt-get update
     sudo apt-get upgrade

    During the installation you will be asked to set a root password for mysql

     sudo apt-get install mysql-server mysql-client libmysqlclient-dev
     service mysql start
  5. Login to mysql and create new database:

     mysql -u root -p

    Enter your mysql root password then

     create database xenforo_db;
  6. Import your xenforo database into the new one:

     mysql -u root -p xenforo_db < /shared/xen_db.sql
     # Enter your mysql root password and wait until it finish
  7. Include the gem in discourse Gemfile:

     echo "gem 'mysql2'" >>Gemfile
  8. Run bundle install:

     bundle install --no-deployment
  9. Edit the importer script and change the following:

    vi /var/www/discourse/script/import_scripts/xenforo.rb
    XENFORO_DB = "xenforo_db"
    @client = Mysql2::Client.new(
      host: "localhost",
      username: "root",
      password: "Your mysql root password",
      database: XENFORO_DB
  10. You are set to go now, run the importer:

    RAILS_ENV=production bundle exec ruby script/import_scripts/xenforo.rb

  11. If you get the error 'Peer authentication failed for user "discourse"':

  12. Edit the file /etc/postgresql/9.5/main/pg_hba.conf

  13. change all ‘peer’ to ‘trust’ and save the file

  14. restart postgres server: service postgresql restart

Run the importer again and this time it should work without any problem, be patient this might take a while based on your database size.

Hope this helps someone. :slight_smile:


So XenForo attachments wil not be converted into Discourse?

I checked the importer script and it appears it doesn’t have an attachment importing code so that would need a custom modification to the script.

Are you thinking about moving your community to Discourse? :grinning:


Yes, XenForo somewhat bored me.

1 Like

If you’d like to have the script also support

  • attachments
  • redirects (see vBulletin redirects for a point of reference)
  • passwords (see discourse-migratepassword – though I’d say this is lower priority since password reset works fine and is often better practice anyhow)

…we recommend posting to our #marketplace offering to pay a developer to work on these improvements. If @Cyb3r is not available for contracting work there’s sure to be others who’d be happy to take this on!


There’s an official XenForo importer, but unfortunately, there is no attachments import feature.

If you’d like for it to support attachments,and have a budget, you can post in #marketplace.

hi, can u help me with converting xenforo to discourse? tnx

What are the list of XenForo Data covered on this import?
I hope to import this list:

XenForo Page Nodes

  • Pages


  • Profile Fields
  • Member Groups
  • Members
  • Status Updates / Profile Comments
  • Status Comments
  • Private Messages
  • Private Message Replies
  • Attachments
  • Member Ranks

XenForo Forums

  • Forums
  • Topics
  • Posts
  • Attachments

Got this error:

# bundle install --no-deployment
Don't run Bundler as root. Bundler can ask for sudo if it is needed, and installing your bundle as root will break this application for all non-root users on this machine.
Your Gemfile has no gem server sources. If you need gems that are not already on your machine, add a line like this to your Gemfile:
source 'https://rubygems.org'
Could not find gem 'mysql2' in any of the gem sources listed in your Gemfile.

Different Error now.

root@discourse-app:/var/www/discourse# RAILS_ENV=production bundle exec ruby script/import_scripts/xenforo.rb
No connection to db, unable to retrieve site settings! (normal when running db:create)
No connection to db, unable to retrieve site settings! (normal when running db:create)
No connection to db, unable to retrieve site settings! (normal when running db:create)
No connection to db, unable to retrieve site settings! (normal when running db:create)
No connection to db, unable to retrieve site settings! (normal when running db:create)
No connection to db, unable to retrieve site settings! (normal when running db:create)
Traceback (most recent call last):
	31: from script/import_scripts/xenforo.rb:3:in `<main>'
	30: from script/import_scripts/xenforo.rb:3:in `require'
	29: from /var/www/discourse/script/import_scripts/base.rb:20:in `<top (required)>'
	28: from /var/www/discourse/script/import_scripts/base.rb:544:in `<class:Base>'
	27: from /var/www/discourse/lib/discourse.rb:411:in `system_user'
	26: from /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.0/lib/active_record/core.rb:196:in `find_by'
	25: from /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.0/lib/active_record/core.rb:196:in `all?'
	24: from /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.0/lib/active_record/core.rb:196:in `each'
	23: from /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.0/lib/active_record/core.rb:196:in `block in find_by'
	22: from /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.0/lib/active_record/model_schema.rb:336:in `columns_hash'
	21: from /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.0/lib/active_record/model_schema.rb:456:in `load_schema'
	20: from /usr/local/lib/ruby/2.5.0/monitor.rb:226:in `mon_synchronize'
	19: from /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.0/lib/active_record/model_schema.rb:459:in `block in load_schema'
	18: from /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.0/lib/active_record/attribute_decorators.rb:51:in `load_schema!'
	17: from /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.0/lib/active_record/attributes.rb:234:in `load_schema!'
	16: from /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.0/lib/active_record/model_schema.rb:466:in `load_schema!'
	15: from /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.0/lib/active_record/connection_handling.rb:90:in `connection'
	14: from /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.0/lib/active_record/connection_handling.rb:118:in `retrieve_connection'
	13: from /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:1008:in `retrieve_connection'
	12: from /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:380:in `connection'
	11: from /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:521:in `checkout'
	10: from /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:793:in `acquire_connection'
	 9: from /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:832:in `try_to_checkout_new_connection'
	 8: from /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:853:in `checkout_new_connection'
	 7: from /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:809:in `new_connection'
	 6: from /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.0/lib/active_record/connection_adapters/postgresql_adapter.rb:40:in `postgresql_connection'
	 5: from /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.0/lib/active_record/connection_adapters/postgresql_adapter.rb:40:in `new'
	 4: from /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.0/lib/active_record/connection_adapters/postgresql_adapter.rb:215:in `initialize'
	 3: from /usr/local/lib/ruby/gems/2.5.0/gems/activerecord-5.2.0/lib/active_record/connection_adapters/postgresql_adapter.rb:684:in `connect'
	 2: from /usr/local/lib/ruby/gems/2.5.0/gems/pg-1.0.0/lib/pg.rb:56:in `connect'
	 1: from /usr/local/lib/ruby/gems/2.5.0/gems/pg-1.0.0/lib/pg.rb:56:in `new'
/usr/local/lib/ruby/gems/2.5.0/gems/pg-1.0.0/lib/pg.rb:56:in `initialize': FATAL:  Peer authentication failed for user "discourse" (PG::ConnectionBad)

Yay no idea how long this will take :smiley:


This is correct right?

Guide needs an update I think.
Before running this, directory must be within /var/www/discourse/.
cd /var/www/discourse/


I used this script today and it worked well. The only issue I had was that it imported all of the banned users and soft deleted (hidden) posts/threads from Xenforo. That made hundreds of spam posts/threads visible in Discourse after the import.

If you have a lot of spam post/threads that you don’t want to import, then here are a few SQL changes you should consider making to the “xenforo.rb” script:

  1. Add the WHERE clause below to ensure only active, unbanned users are imported.

    def import_users
     puts '', "creating users"
     total_count = mysql_query("SELECT count(*) count FROM #{TABLE_PREFIX}user;").first['count']
     batches(BATCH_SIZE) do |offset|
       results = mysql_query(
         "SELECT user_id id, username, email, custom_title title, register_date created_at,
                 last_activity last_visit_time, user_group_id, is_moderator, is_admin, is_staff
          FROM #{TABLE_PREFIX}user
          WHERE user_state = 'valid' AND is_banned = 0
          LIMIT #{BATCH_SIZE}
          OFFSET #{offset};")

    2. Modify the WHERE clause below to ensure only visible threads/posts are imported.
    def import_posts
     puts "", "creating topics and posts"
     total_count = mysql_query("SELECT count(*) count from #{TABLE_PREFIX}post").first["count"]
     posts_sql = "
         SELECT p.post_id id,
                t.thread_id topic_id,
                #{@prefix_as_category ? 't.prefix_id' : 't.node_id'} category_id,
                t.title title,
                t.first_post_id first_post_id,
                p.user_id user_id,
                p.message raw,
                p.post_date created_at
         FROM #{TABLE_PREFIX}post p,
              #{TABLE_PREFIX}thread t
         WHERE p.thread_id = t.thread_id AND p.message_state = 'visible' AND t.discussion_state = 'visible'
         ORDER BY p.post_date
         LIMIT #{BATCH_SIZE}" # needs OFFSET

Sure thanks for that, can we fold those changes into our script @techAPJ?


Done via:


Thanks for the tips @msinger. :+1:


8 posts were split to a new topic: Help migrating Xenforo

Hi guy’s,

I sucssesufuly migrated Xenforo DB with 1500 memebr and ~75.000 posts to new servere where is Discourse.

One question about attachment… from script I can see that if attachments folder exist then script will do import, my question is where I need to put attac from old server in:

a) In Docker part of server (somewhere)
b) In origin location /var/discourse/shared/standalone/tmp/attachments


1 Like

Anyone know path???

1 Like